Compare commits

...

226 commits

Author SHA1 Message Date
dependabot[bot]
09e96d92d6
build(deps): bump mkdocstrings from 0.29.1 to 0.30.0 (#1313)
Some checks failed
Docs - build & deploy / docs (push) Has been cancelled
Run tests / build (ubuntu-latest, 3.10) (push) Has been cancelled
Run tests / build (ubuntu-latest, 3.11) (push) Has been cancelled
Run tests / build (ubuntu-latest, 3.12) (push) Has been cancelled
Run tests / build (ubuntu-latest, 3.13) (push) Has been cancelled
Run tests / build (ubuntu-latest, 3.8) (push) Has been cancelled
Run tests / build (ubuntu-latest, 3.9) (push) Has been cancelled
Run tests / build (windows-latest, 3.10) (push) Has been cancelled
Run tests / build (windows-latest, 3.11) (push) Has been cancelled
Run tests / build (windows-latest, 3.12) (push) Has been cancelled
Run tests / build (windows-latest, 3.13) (push) Has been cancelled
Run tests / build (windows-latest, 3.8) (push) Has been cancelled
Run tests / build (windows-latest, 3.9) (push) Has been cancelled
Run tests / test_docs (3.13) (push) Has been cancelled
Run tests / test_sampleproject (3.13) (push) Has been cancelled
Bumps [mkdocstrings](https://github.com/mkdocstrings/mkdocstrings) from 0.29.1 to 0.30.0.
- [Release notes](https://github.com/mkdocstrings/mkdocstrings/releases)
- [Changelog](https://github.com/mkdocstrings/mkdocstrings/blob/main/CHANGELOG.md)
- [Commits](https://github.com/mkdocstrings/mkdocstrings/compare/0.29.1...0.30.0)

---
updated-dependencies:
- dependency-name: mkdocstrings
  dependency-version: 0.30.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-07-29 22:32:18 +02:00
dependabot[bot]
df8c6ab1a4
build(deps): bump griffe from 1.7.3 to 1.9.0 (#1314)
Bumps [griffe](https://github.com/mkdocstrings/griffe) from 1.7.3 to 1.9.0.
- [Release notes](https://github.com/mkdocstrings/griffe/releases)
- [Changelog](https://github.com/mkdocstrings/griffe/blob/main/CHANGELOG.md)
- [Commits](https://github.com/mkdocstrings/griffe/compare/1.7.3...1.9.0)

---
updated-dependencies:
- dependency-name: griffe
  dependency-version: 1.9.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-07-29 22:31:59 +02:00
dependabot[bot]
10e1a25b26
build(deps): bump gitpython from 3.1.44 to 3.1.45 (#1316)
Bumps [gitpython](https://github.com/gitpython-developers/GitPython) from 3.1.44 to 3.1.45.
- [Release notes](https://github.com/gitpython-developers/GitPython/releases)
- [Changelog](https://github.com/gitpython-developers/GitPython/blob/main/CHANGES)
- [Commits](https://github.com/gitpython-developers/GitPython/compare/3.1.44...3.1.45)

---
updated-dependencies:
- dependency-name: gitpython
  dependency-version: 3.1.45
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-07-29 22:31:40 +02:00
github-actions[bot]
9e280f2e61
Merge pull request #1312 from django-components/dependabot/pip/pymdown-extensions-10.16.1
build(deps): bump pymdown-extensions from 10.16 to 10.16.1
2025-07-29 22:31:25 +02:00
dependabot[bot]
de1ebcd071
build(deps): bump pymdown-extensions from 10.16 to 10.16.1
Bumps [pymdown-extensions](https://github.com/facelessuser/pymdown-extensions) from 10.16 to 10.16.1.
- [Release notes](https://github.com/facelessuser/pymdown-extensions/releases)
- [Commits](https://github.com/facelessuser/pymdown-extensions/compare/10.16...10.16.1)

---
updated-dependencies:
- dependency-name: pymdown-extensions
  dependency-version: 10.16.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-28 18:23:44 +00:00
dependabot[bot]
740a8c5f1c
build(deps): bump virtualenv from 20.31.2 to 20.32.0 (#1307)
Some checks failed
Docs - build & deploy / docs (push) Has been cancelled
Run tests / build (ubuntu-latest, 3.10) (push) Has been cancelled
Run tests / build (ubuntu-latest, 3.11) (push) Has been cancelled
Run tests / build (ubuntu-latest, 3.12) (push) Has been cancelled
Run tests / build (ubuntu-latest, 3.13) (push) Has been cancelled
Run tests / build (ubuntu-latest, 3.8) (push) Has been cancelled
Run tests / build (ubuntu-latest, 3.9) (push) Has been cancelled
Run tests / build (windows-latest, 3.10) (push) Has been cancelled
Run tests / build (windows-latest, 3.11) (push) Has been cancelled
Run tests / build (windows-latest, 3.12) (push) Has been cancelled
Run tests / build (windows-latest, 3.13) (push) Has been cancelled
Run tests / build (windows-latest, 3.8) (push) Has been cancelled
Run tests / build (windows-latest, 3.9) (push) Has been cancelled
Run tests / test_docs (3.13) (push) Has been cancelled
Run tests / test_sampleproject (3.13) (push) Has been cancelled
---
updated-dependencies:
- dependency-name: virtualenv
  dependency-version: 20.32.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-07-22 09:29:13 +02:00
github-actions[bot]
7a260f7e47
Merge pull request #1305 from django-components/dependabot/pip/mypy-1.17.0
build(deps-dev): bump mypy from 1.16.1 to 1.17.0
2025-07-22 09:28:50 +02:00
dependabot[bot]
6772736f4c
build(deps-dev): bump mypy from 1.16.1 to 1.17.0
---
updated-dependencies:
- dependency-name: mypy
  dependency-version: 1.17.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-21 19:39:57 +00:00
components-release-bot
8246f908bf Add benchmark results for 0.141.2
Some checks failed
Docs - build & deploy / docs (push) Has been cancelled
Run tests / build (ubuntu-latest, 3.10) (push) Has been cancelled
Run tests / build (ubuntu-latest, 3.11) (push) Has been cancelled
Run tests / build (ubuntu-latest, 3.12) (push) Has been cancelled
Run tests / build (ubuntu-latest, 3.13) (push) Has been cancelled
Run tests / build (ubuntu-latest, 3.8) (push) Has been cancelled
Run tests / build (ubuntu-latest, 3.9) (push) Has been cancelled
Run tests / build (windows-latest, 3.10) (push) Has been cancelled
Run tests / build (windows-latest, 3.11) (push) Has been cancelled
Run tests / build (windows-latest, 3.12) (push) Has been cancelled
Run tests / build (windows-latest, 3.13) (push) Has been cancelled
Run tests / build (windows-latest, 3.8) (push) Has been cancelled
Run tests / build (windows-latest, 3.9) (push) Has been cancelled
Run tests / test_docs (3.13) (push) Has been cancelled
Run tests / test_sampleproject (3.13) (push) Has been cancelled
2025-07-20 22:18:32 +00:00
github-actions[bot]
5d7e235725
Merge pull request #1297 from django-components/dependabot/pip/asgiref-3.9.1
build(deps): bump asgiref from 3.9.0 to 3.9.1
2025-07-21 00:05:08 +02:00
dependabot[bot]
2a44fbec27
build(deps): bump asgiref from 3.9.0 to 3.9.1
Bumps [asgiref](https://github.com/django/asgiref) from 3.9.0 to 3.9.1.
- [Changelog](https://github.com/django/asgiref/blob/main/CHANGELOG.txt)
- [Commits](https://github.com/django/asgiref/compare/3.9.0...3.9.1)

---
updated-dependencies:
- dependency-name: asgiref
  dependency-version: 3.9.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-20 21:55:33 +00:00
github-actions[bot]
f4347dd9d9
Merge pull request #1298 from django-components/dependabot/pip/certifi-2025.7.14
build(deps): bump certifi from 2025.6.15 to 2025.7.14
2025-07-20 23:54:26 +02:00
Juro Oravec
65373ec3e8
Merge branch 'master' into dependabot/pip/certifi-2025.7.14 2025-07-20 23:43:21 +02:00
Juro Oravec
81c0d419b4
fix: Fix bug where JS and CSS were missing when {% component %} tag was inside {% include %} tag (#1300)
* fix: Fix bug where JS and CSS were missing when `{% component %}` tag was inside `{% include %}` tag

* refactor: fix mypy error
2025-07-20 23:42:59 +02:00
Juro Oravec
672811b8b4
Update devcontainer.json (#1302) 2025-07-20 17:15:54 +02:00
Juro Oravec
f8c8292441
Create devcontainer.json (#1301)
Some checks are pending
Docs - build & deploy / docs (push) Waiting to run
Run tests / build (ubuntu-latest, 3.10) (push) Waiting to run
Run tests / build (ubuntu-latest, 3.11) (push) Waiting to run
Run tests / build (ubuntu-latest, 3.12) (push) Waiting to run
Run tests / build (ubuntu-latest, 3.13) (push) Waiting to run
Run tests / build (ubuntu-latest, 3.8) (push) Waiting to run
Run tests / build (ubuntu-latest, 3.9) (push) Waiting to run
Run tests / build (windows-latest, 3.10) (push) Waiting to run
Run tests / build (windows-latest, 3.11) (push) Waiting to run
Run tests / build (windows-latest, 3.12) (push) Waiting to run
Run tests / build (windows-latest, 3.13) (push) Waiting to run
Run tests / build (windows-latest, 3.8) (push) Waiting to run
Run tests / build (windows-latest, 3.9) (push) Waiting to run
Run tests / test_docs (3.13) (push) Waiting to run
Run tests / test_sampleproject (3.13) (push) Waiting to run
* Create devcontainer.json

* Update devcontainer.json
2025-07-20 16:47:10 +02:00
Antoliny Lee
8cba8702a3
docs: fix wrong link to template variable page in sidebar (#1295)
Co-authored-by: Juro Oravec <juraj.oravec.josefson@gmail.com>
2025-07-20 13:22:32 +02:00
dependabot[bot]
fb4b654433
build(deps): bump certifi from 2025.6.15 to 2025.7.14
Bumps [certifi](https://github.com/certifi/python-certifi) from 2025.6.15 to 2025.7.14.
- [Commits](https://github.com/certifi/python-certifi/compare/2025.06.15...2025.07.14)

---
updated-dependencies:
- dependency-name: certifi
  dependency-version: 2025.7.14
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-14 20:58:02 +00:00
dependabot[bot]
5f8ec71358
build(deps): bump asgiref from 3.8.1 to 3.9.0 (#1293)
Some checks failed
Docs - build & deploy / docs (push) Has been cancelled
Run tests / build (ubuntu-latest, 3.11) (push) Has been cancelled
Run tests / build (ubuntu-latest, 3.10) (push) Has been cancelled
Run tests / build (ubuntu-latest, 3.12) (push) Has been cancelled
Run tests / build (ubuntu-latest, 3.13) (push) Has been cancelled
Run tests / build (ubuntu-latest, 3.8) (push) Has been cancelled
Run tests / build (ubuntu-latest, 3.9) (push) Has been cancelled
Run tests / build (windows-latest, 3.10) (push) Has been cancelled
Run tests / build (windows-latest, 3.11) (push) Has been cancelled
Run tests / build (windows-latest, 3.12) (push) Has been cancelled
Run tests / build (windows-latest, 3.13) (push) Has been cancelled
Run tests / build (windows-latest, 3.8) (push) Has been cancelled
Run tests / build (windows-latest, 3.9) (push) Has been cancelled
Run tests / test_docs (3.13) (push) Has been cancelled
Run tests / test_sampleproject (3.13) (push) Has been cancelled
Bumps [asgiref](https://github.com/django/asgiref) from 3.8.1 to 3.9.0.
- [Changelog](https://github.com/django/asgiref/blob/main/CHANGELOG.txt)
- [Commits](https://github.com/django/asgiref/compare/3.8.1...3.9.0)

---
updated-dependencies:
- dependency-name: asgiref
  dependency-version: 3.9.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-07-08 10:24:15 +02:00
dependabot[bot]
f15f1e967a
build(deps): bump mkdocs-material from 9.6.14 to 9.6.15 (#1291)
Bumps [mkdocs-material](https://github.com/squidfunk/mkdocs-material) from 9.6.14 to 9.6.15.
- [Release notes](https://github.com/squidfunk/mkdocs-material/releases)
- [Changelog](https://github.com/squidfunk/mkdocs-material/blob/master/CHANGELOG)
- [Commits](https://github.com/squidfunk/mkdocs-material/compare/9.6.14...9.6.15)

---
updated-dependencies:
- dependency-name: mkdocs-material
  dependency-version: 9.6.15
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-07-08 10:24:00 +02:00
Antoliny Lee
b7b0d250c4
docs: add next step line for Adding slots to Rendering components (#1289)
Some checks are pending
Docs - build & deploy / docs (push) Waiting to run
Run tests / build (ubuntu-latest, 3.10) (push) Waiting to run
Run tests / build (ubuntu-latest, 3.11) (push) Waiting to run
Run tests / build (ubuntu-latest, 3.12) (push) Waiting to run
Run tests / build (ubuntu-latest, 3.13) (push) Waiting to run
Run tests / build (ubuntu-latest, 3.8) (push) Waiting to run
Run tests / build (ubuntu-latest, 3.9) (push) Waiting to run
Run tests / build (windows-latest, 3.10) (push) Waiting to run
Run tests / build (windows-latest, 3.11) (push) Waiting to run
Run tests / build (windows-latest, 3.12) (push) Waiting to run
Run tests / build (windows-latest, 3.13) (push) Waiting to run
Run tests / build (windows-latest, 3.8) (push) Waiting to run
Run tests / build (windows-latest, 3.9) (push) Waiting to run
Run tests / test_docs (3.13) (push) Waiting to run
Run tests / test_sampleproject (3.13) (push) Waiting to run
2025-07-07 18:30:15 +02:00
Antoliny Lee
0c7e17d8cf
docs: add section numbering to Rendering components page (#1288) 2025-07-07 18:28:12 +02:00
Antoliny Lee
514b8206b5
docs: fix incorrect numbering in adding slots section (#1287) 2025-07-07 18:25:52 +02:00
Antoliny Lee
bcc7bac48d
docs: fix parametrising components section next step line position (#1286) 2025-07-07 18:25:19 +02:00
Juro Oravec
39e9ee0dc3
Update community.md (#1290) 2025-07-07 18:24:03 +02:00
Antoliny Lee
df7f94cb2d
docs: fix component_library link (#1285) 2025-07-07 18:23:45 +02:00
Antoliny Lee
9a4f835201
docs: add register decorator to Calendar class in Quickstart section (#1284) 2025-07-07 16:45:06 +02:00
components-release-bot
59e5fb3b38 Add benchmark results for 0.141.1
Some checks failed
Docs - build & deploy / docs (push) Has been cancelled
Run tests / build (ubuntu-latest, 3.10) (push) Has been cancelled
Run tests / build (ubuntu-latest, 3.11) (push) Has been cancelled
Run tests / build (ubuntu-latest, 3.12) (push) Has been cancelled
Run tests / build (ubuntu-latest, 3.13) (push) Has been cancelled
Run tests / build (ubuntu-latest, 3.8) (push) Has been cancelled
Run tests / build (ubuntu-latest, 3.9) (push) Has been cancelled
Run tests / build (windows-latest, 3.10) (push) Has been cancelled
Run tests / build (windows-latest, 3.11) (push) Has been cancelled
Run tests / build (windows-latest, 3.12) (push) Has been cancelled
Run tests / build (windows-latest, 3.13) (push) Has been cancelled
Run tests / build (windows-latest, 3.8) (push) Has been cancelled
Run tests / build (windows-latest, 3.9) (push) Has been cancelled
Run tests / test_docs (3.13) (push) Has been cancelled
Run tests / test_sampleproject (3.13) (push) Has been cancelled
2025-07-03 10:34:55 +00:00
Juro Oravec
c692b7a310
refactor: Fix #1277 + Cache components' JS/CSS scripts at class creation (#1283)
* refactor: Cache components' JS and CSS scripts at class creation time

* refactor: add test for no template_rendered signal for component with no template
2025-07-03 12:27:21 +02:00
github-actions[bot]
007009a480
Merge pull request #1282 from django-components/dependabot/pip/pillow-11.3.0
Some checks are pending
Docs - build & deploy / docs (push) Waiting to run
Run tests / build (windows-latest, 3.8) (push) Waiting to run
Run tests / build (windows-latest, 3.9) (push) Waiting to run
Run tests / test_docs (3.13) (push) Waiting to run
Run tests / build (ubuntu-latest, 3.13) (push) Waiting to run
Run tests / build (ubuntu-latest, 3.8) (push) Waiting to run
Run tests / build (ubuntu-latest, 3.9) (push) Waiting to run
Run tests / build (windows-latest, 3.10) (push) Waiting to run
Run tests / build (windows-latest, 3.11) (push) Waiting to run
Run tests / build (windows-latest, 3.12) (push) Waiting to run
Run tests / build (windows-latest, 3.13) (push) Waiting to run
Run tests / build (ubuntu-latest, 3.10) (push) Waiting to run
Run tests / build (ubuntu-latest, 3.11) (push) Waiting to run
Run tests / build (ubuntu-latest, 3.12) (push) Waiting to run
Run tests / test_sampleproject (3.13) (push) Waiting to run
build(deps): bump pillow from 11.2.1 to 11.3.0
2025-07-03 08:06:38 +02:00
dependabot[bot]
f8e2721244
build(deps): bump pillow from 11.2.1 to 11.3.0
Bumps [pillow](https://github.com/python-pillow/Pillow) from 11.2.1 to 11.3.0.
- [Release notes](https://github.com/python-pillow/Pillow/releases)
- [Changelog](https://github.com/python-pillow/Pillow/blob/main/CHANGES.rst)
- [Commits](https://github.com/python-pillow/Pillow/compare/11.2.1...11.3.0)

---
updated-dependencies:
- dependency-name: pillow
  dependency-version: 11.3.0
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-01 17:32:06 +00:00
dependabot[bot]
77fd149346
build(deps): bump certifi from 2025.4.26 to 2025.6.15 (#1278)
Some checks failed
Docs - build & deploy / docs (push) Has been cancelled
Run tests / build (ubuntu-latest, 3.10) (push) Has been cancelled
Run tests / build (ubuntu-latest, 3.11) (push) Has been cancelled
Run tests / build (ubuntu-latest, 3.12) (push) Has been cancelled
Run tests / build (ubuntu-latest, 3.13) (push) Has been cancelled
Run tests / build (ubuntu-latest, 3.8) (push) Has been cancelled
Run tests / build (ubuntu-latest, 3.9) (push) Has been cancelled
Run tests / build (windows-latest, 3.10) (push) Has been cancelled
Run tests / build (windows-latest, 3.11) (push) Has been cancelled
Run tests / build (windows-latest, 3.12) (push) Has been cancelled
Run tests / build (windows-latest, 3.13) (push) Has been cancelled
Run tests / build (windows-latest, 3.8) (push) Has been cancelled
Run tests / build (windows-latest, 3.9) (push) Has been cancelled
Run tests / test_docs (3.13) (push) Has been cancelled
Run tests / test_sampleproject (3.13) (push) Has been cancelled
Bumps [certifi](https://github.com/certifi/python-certifi) from 2025.4.26 to 2025.6.15.
- [Commits](https://github.com/certifi/python-certifi/compare/2025.04.26...2025.06.15)

---
updated-dependencies:
- dependency-name: certifi
  dependency-version: 2025.6.15
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-07-01 13:10:10 +02:00
dependabot[bot]
055dd07694
build(deps): bump markdown-exec from 1.10.3 to 1.11.0 (#1279)
Bumps [markdown-exec](https://github.com/pawamoy/markdown-exec) from 1.10.3 to 1.11.0.
- [Release notes](https://github.com/pawamoy/markdown-exec/releases)
- [Changelog](https://github.com/pawamoy/markdown-exec/blob/main/CHANGELOG.md)
- [Commits](https://github.com/pawamoy/markdown-exec/compare/1.10.3...1.11.0)

---
updated-dependencies:
- dependency-name: markdown-exec
  dependency-version: 1.11.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-07-01 13:09:59 +02:00
github-actions[bot]
e7883d0b54
Merge pull request #1280 from django-components/dependabot/pip/django-4.2.23
build(deps): bump django from 4.2.22 to 4.2.23
2025-07-01 13:09:33 +02:00
dependabot[bot]
64322a7b84
build(deps): bump django from 4.2.22 to 4.2.23
Bumps [django](https://github.com/django/django) from 4.2.22 to 4.2.23.
- [Commits](https://github.com/django/django/compare/4.2.22...4.2.23)

---
updated-dependencies:
- dependency-name: django
  dependency-version: 4.2.23
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-30 20:21:35 +00:00
dependabot[bot]
b021b54ad4
build(deps): bump pygments from 2.19.1 to 2.19.2 (#1272)
Some checks failed
Docs - build & deploy / docs (push) Has been cancelled
Run tests / build (ubuntu-latest, 3.10) (push) Has been cancelled
Run tests / build (ubuntu-latest, 3.11) (push) Has been cancelled
Run tests / build (ubuntu-latest, 3.12) (push) Has been cancelled
Run tests / build (ubuntu-latest, 3.13) (push) Has been cancelled
Run tests / build (ubuntu-latest, 3.8) (push) Has been cancelled
Run tests / build (ubuntu-latest, 3.9) (push) Has been cancelled
Run tests / build (windows-latest, 3.10) (push) Has been cancelled
Run tests / build (windows-latest, 3.11) (push) Has been cancelled
Run tests / build (windows-latest, 3.12) (push) Has been cancelled
Run tests / build (windows-latest, 3.13) (push) Has been cancelled
Run tests / build (windows-latest, 3.8) (push) Has been cancelled
Run tests / build (windows-latest, 3.9) (push) Has been cancelled
Run tests / test_docs (3.13) (push) Has been cancelled
Run tests / test_sampleproject (3.13) (push) Has been cancelled
Bumps [pygments](https://github.com/pygments/pygments) from 2.19.1 to 2.19.2.
- [Release notes](https://github.com/pygments/pygments/releases)
- [Changelog](https://github.com/pygments/pygments/blob/master/CHANGES)
- [Commits](https://github.com/pygments/pygments/compare/2.19.1...2.19.2)

---
updated-dependencies:
- dependency-name: pygments
  dependency-version: 2.19.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-06-24 11:16:31 +02:00
dependabot[bot]
212336c99a
build(deps): bump wcmatch from 10.0 to 10.1 (#1276)
---
updated-dependencies:
- dependency-name: wcmatch
  dependency-version: '10.1'
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Juro Oravec <juraj.oravec.josefson@gmail.com>
2025-06-24 11:16:13 +02:00
dependabot[bot]
8ceb143fb3
build(deps): bump bracex from 2.5.post1 to 2.6 (#1274)
Bumps [bracex](https://github.com/facelessuser/bracex) from 2.5.post1 to 2.6.
- [Release notes](https://github.com/facelessuser/bracex/releases)
- [Commits](https://github.com/facelessuser/bracex/compare/2.5.post1...2.6)

---
updated-dependencies:
- dependency-name: bracex
  dependency-version: '2.6'
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-06-24 11:15:59 +02:00
github-actions[bot]
15669a2fb1
Merge pull request #1268 from django-components/dependabot/pip/flake8-7.3.0
Some checks are pending
Docs - build & deploy / docs (push) Waiting to run
Run tests / build (ubuntu-latest, 3.8) (push) Waiting to run
Run tests / build (ubuntu-latest, 3.10) (push) Waiting to run
Run tests / build (ubuntu-latest, 3.11) (push) Waiting to run
Run tests / build (ubuntu-latest, 3.12) (push) Waiting to run
Run tests / build (ubuntu-latest, 3.13) (push) Waiting to run
Run tests / build (ubuntu-latest, 3.9) (push) Waiting to run
Run tests / build (windows-latest, 3.10) (push) Waiting to run
Run tests / build (windows-latest, 3.11) (push) Waiting to run
Run tests / build (windows-latest, 3.12) (push) Waiting to run
Run tests / build (windows-latest, 3.13) (push) Waiting to run
Run tests / build (windows-latest, 3.8) (push) Waiting to run
Run tests / build (windows-latest, 3.9) (push) Waiting to run
Run tests / test_docs (3.13) (push) Waiting to run
Run tests / test_sampleproject (3.13) (push) Waiting to run
build(deps-dev): bump flake8 from 7.2.0 to 7.3.0
2025-06-23 22:58:56 +02:00
dependabot[bot]
10a9e555c0
build(deps-dev): bump flake8 from 7.2.0 to 7.3.0
Bumps [flake8](https://github.com/pycqa/flake8) from 7.2.0 to 7.3.0.
- [Commits](https://github.com/pycqa/flake8/compare/7.2.0...7.3.0)

---
updated-dependencies:
- dependency-name: flake8
  dependency-version: 7.3.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-23 20:30:50 +00:00
dependabot[bot]
4d9c814e32
build(deps): bump mkdocs-include-markdown-plugin from 7.1.5 to 7.1.6 (#1267)
Bumps [mkdocs-include-markdown-plugin](https://github.com/mondeja/mkdocs-include-markdown-plugin) from 7.1.5 to 7.1.6.
- [Release notes](https://github.com/mondeja/mkdocs-include-markdown-plugin/releases)
- [Commits](https://github.com/mondeja/mkdocs-include-markdown-plugin/compare/v7.1.5...v7.1.6)

---
updated-dependencies:
- dependency-name: mkdocs-include-markdown-plugin
  dependency-version: 7.1.6
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Juro Oravec <juraj.oravec.josefson@gmail.com>
2025-06-23 22:28:00 +02:00
dependabot[bot]
68089daefb
build(deps): bump markdown from 3.8 to 3.8.2 (#1269)
Bumps [markdown](https://github.com/Python-Markdown/markdown) from 3.8 to 3.8.2.
- [Release notes](https://github.com/Python-Markdown/markdown/releases)
- [Changelog](https://github.com/Python-Markdown/markdown/blob/master/docs/changelog.md)
- [Commits](https://github.com/Python-Markdown/markdown/compare/3.8...3.8.2)

---
updated-dependencies:
- dependency-name: markdown
  dependency-version: 3.8.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-06-23 22:27:26 +02:00
github-actions[bot]
b0da3b166d
Merge pull request #1271 from django-components/dependabot/pip/pymdown-extensions-10.16
build(deps): bump pymdown-extensions from 10.15 to 10.16
2025-06-23 22:26:08 +02:00
dependabot[bot]
5e34ec28cf
build(deps): bump pymdown-extensions from 10.15 to 10.16
Bumps [pymdown-extensions](https://github.com/facelessuser/pymdown-extensions) from 10.15 to 10.16.
- [Release notes](https://github.com/facelessuser/pymdown-extensions/releases)
- [Commits](https://github.com/facelessuser/pymdown-extensions/compare/10.15...10.16)

---
updated-dependencies:
- dependency-name: pymdown-extensions
  dependency-version: '10.16'
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-23 19:58:23 +00:00
github-actions[bot]
b0ef1f2a4c
Merge pull request #1266 from django-components/dependabot/pip/urllib3-2.5.0
Some checks failed
Docs - build & deploy / docs (push) Has been cancelled
Run tests / build (ubuntu-latest, 3.10) (push) Has been cancelled
Run tests / build (ubuntu-latest, 3.11) (push) Has been cancelled
Run tests / build (ubuntu-latest, 3.12) (push) Has been cancelled
Run tests / build (ubuntu-latest, 3.13) (push) Has been cancelled
Run tests / build (ubuntu-latest, 3.8) (push) Has been cancelled
Run tests / build (ubuntu-latest, 3.9) (push) Has been cancelled
Run tests / build (windows-latest, 3.10) (push) Has been cancelled
Run tests / build (windows-latest, 3.11) (push) Has been cancelled
Run tests / build (windows-latest, 3.12) (push) Has been cancelled
Run tests / build (windows-latest, 3.13) (push) Has been cancelled
Run tests / build (windows-latest, 3.8) (push) Has been cancelled
Run tests / build (windows-latest, 3.9) (push) Has been cancelled
Run tests / test_docs (3.13) (push) Has been cancelled
Run tests / test_sampleproject (3.13) (push) Has been cancelled
build(deps): bump urllib3 from 2.4.0 to 2.5.0
2025-06-19 09:01:48 +02:00
dependabot[bot]
14a8358b08
build(deps): bump urllib3 from 2.4.0 to 2.5.0
Bumps [urllib3](https://github.com/urllib3/urllib3) from 2.4.0 to 2.5.0.
- [Release notes](https://github.com/urllib3/urllib3/releases)
- [Changelog](https://github.com/urllib3/urllib3/blob/main/CHANGES.rst)
- [Commits](https://github.com/urllib3/urllib3/compare/2.4.0...2.5.0)

---
updated-dependencies:
- dependency-name: urllib3
  dependency-version: 2.5.0
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-19 03:57:36 +00:00
github-actions[bot]
b09c83b128
Merge pull request #1262 from django-components/dependabot/pip/django-4.2.23
Some checks failed
Docs - build & deploy / docs (push) Has been cancelled
Run tests / build (ubuntu-latest, 3.10) (push) Has been cancelled
Run tests / build (ubuntu-latest, 3.11) (push) Has been cancelled
Run tests / build (ubuntu-latest, 3.12) (push) Has been cancelled
Run tests / build (ubuntu-latest, 3.13) (push) Has been cancelled
Run tests / build (ubuntu-latest, 3.8) (push) Has been cancelled
Run tests / build (ubuntu-latest, 3.9) (push) Has been cancelled
Run tests / build (windows-latest, 3.10) (push) Has been cancelled
Run tests / build (windows-latest, 3.11) (push) Has been cancelled
Run tests / build (windows-latest, 3.12) (push) Has been cancelled
Run tests / build (windows-latest, 3.13) (push) Has been cancelled
Run tests / build (windows-latest, 3.8) (push) Has been cancelled
Run tests / build (windows-latest, 3.9) (push) Has been cancelled
Run tests / test_docs (3.13) (push) Has been cancelled
Run tests / test_sampleproject (3.13) (push) Has been cancelled
build(deps): bump django from 4.2.22 to 4.2.23
2025-06-17 12:50:00 +02:00
dependabot[bot]
6e86851894
build(deps): bump django from 4.2.22 to 4.2.23
Bumps [django](https://github.com/django/django) from 4.2.22 to 4.2.23.
- [Commits](https://github.com/django/django/compare/4.2.22...4.2.23)

---
updated-dependencies:
- dependency-name: django
  dependency-version: 4.2.23
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-17 05:32:21 +00:00
github-actions[bot]
b9e8b8e569
Merge pull request #1261 from django-components/dependabot/pip/mkdocs-git-authors-plugin-0.10.0
Some checks are pending
Docs - build & deploy / docs (push) Waiting to run
Run tests / build (ubuntu-latest, 3.10) (push) Waiting to run
Run tests / build (ubuntu-latest, 3.11) (push) Waiting to run
Run tests / build (ubuntu-latest, 3.12) (push) Waiting to run
Run tests / build (ubuntu-latest, 3.13) (push) Waiting to run
Run tests / build (ubuntu-latest, 3.8) (push) Waiting to run
Run tests / build (ubuntu-latest, 3.9) (push) Waiting to run
Run tests / build (windows-latest, 3.10) (push) Waiting to run
Run tests / build (windows-latest, 3.11) (push) Waiting to run
Run tests / build (windows-latest, 3.12) (push) Waiting to run
Run tests / build (windows-latest, 3.13) (push) Waiting to run
Run tests / build (windows-latest, 3.8) (push) Waiting to run
Run tests / build (windows-latest, 3.9) (push) Waiting to run
Run tests / test_docs (3.13) (push) Waiting to run
Run tests / test_sampleproject (3.13) (push) Waiting to run
build(deps): bump mkdocs-git-authors-plugin from 0.9.6 to 0.10.0
2025-06-17 05:31:13 +00:00
dependabot[bot]
1db72b6e96
build(deps): bump mkdocs-git-authors-plugin from 0.9.6 to 0.10.0
Bumps [mkdocs-git-authors-plugin](https://github.com/timvink/mkdocs-git-authors-plugin) from 0.9.6 to 0.10.0.
- [Release notes](https://github.com/timvink/mkdocs-git-authors-plugin/releases)
- [Commits](https://github.com/timvink/mkdocs-git-authors-plugin/compare/v0.9.6...v0.10.0)

---
updated-dependencies:
- dependency-name: mkdocs-git-authors-plugin
  dependency-version: 0.10.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-17 05:22:16 +00:00
github-actions[bot]
cd1643ad23
Merge pull request #1263 from django-components/dependabot/pip/mypy-1.16.1
build(deps-dev): bump mypy from 1.16.0 to 1.16.1
2025-06-17 06:21:07 +01:00
dependabot[bot]
6d754acb99
build(deps-dev): bump mypy from 1.16.0 to 1.16.1
Bumps [mypy](https://github.com/python/mypy) from 1.16.0 to 1.16.1.
- [Changelog](https://github.com/python/mypy/blob/master/CHANGELOG.md)
- [Commits](https://github.com/python/mypy/compare/v1.16.0...v1.16.1)

---
updated-dependencies:
- dependency-name: mypy
  dependency-version: 1.16.1
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-16 19:09:24 +00:00
Dylan Castillo
50d71b6187
Merge pull request #1254 from dylanjcastillo/master
Some checks failed
Docs - build & deploy / docs (push) Has been cancelled
Run tests / build (ubuntu-latest, 3.10) (push) Has been cancelled
Run tests / build (ubuntu-latest, 3.11) (push) Has been cancelled
Run tests / build (ubuntu-latest, 3.12) (push) Has been cancelled
Run tests / build (ubuntu-latest, 3.13) (push) Has been cancelled
Run tests / build (ubuntu-latest, 3.8) (push) Has been cancelled
Run tests / build (ubuntu-latest, 3.9) (push) Has been cancelled
Run tests / build (windows-latest, 3.10) (push) Has been cancelled
Run tests / build (windows-latest, 3.11) (push) Has been cancelled
Run tests / build (windows-latest, 3.12) (push) Has been cancelled
Run tests / build (windows-latest, 3.13) (push) Has been cancelled
Run tests / build (windows-latest, 3.8) (push) Has been cancelled
Run tests / build (windows-latest, 3.9) (push) Has been cancelled
Run tests / test_docs (3.13) (push) Has been cancelled
Run tests / test_sampleproject (3.13) (push) Has been cancelled
chore: remove live demo link
2025-06-10 19:16:47 +02:00
Dylan Castillo
bdfe966919 Remove live demo link 2025-06-10 14:13:07 +02:00
components-release-bot
d537ba1ee5 Add benchmark results for 0.141.0
Some checks are pending
Docs - build & deploy / docs (push) Waiting to run
Run tests / build (ubuntu-latest, 3.10) (push) Waiting to run
Run tests / build (ubuntu-latest, 3.11) (push) Waiting to run
Run tests / build (ubuntu-latest, 3.12) (push) Waiting to run
Run tests / build (ubuntu-latest, 3.13) (push) Waiting to run
Run tests / build (ubuntu-latest, 3.8) (push) Waiting to run
Run tests / build (ubuntu-latest, 3.9) (push) Waiting to run
Run tests / build (windows-latest, 3.10) (push) Waiting to run
Run tests / build (windows-latest, 3.11) (push) Waiting to run
Run tests / build (windows-latest, 3.12) (push) Waiting to run
Run tests / build (windows-latest, 3.13) (push) Waiting to run
Run tests / build (windows-latest, 3.8) (push) Waiting to run
Run tests / build (windows-latest, 3.9) (push) Waiting to run
Run tests / test_docs (3.13) (push) Waiting to run
Run tests / test_sampleproject (3.13) (push) Waiting to run
2025-06-10 09:21:24 +00:00
Juro Oravec
06c89cf9e8
chore: bump v0.141.0 (#1253) 2025-06-10 11:12:49 +02:00
Juro Oravec
3fc96daa9b
refactor: in components ext run, display only those extensions that actually have subcommands (#1251) 2025-06-10 11:11:24 +02:00
Juro Oravec
458e1894db
refactor: fix wrongly initiated settings (#1250)
* refactor: fix wrongly initiated settings

* refacttor: remove `_load_settings()` from apps.py

* refactor: fix building of docs + update titles in API reference

* refactor: fix docs build error

* refactor: use EXTENSIONS_DEFAULTS

* refactor: update titles
2025-06-10 10:12:48 +02:00
github-actions[bot]
2350a6b6c4
Merge pull request #1245 from django-components/dependabot/pip/requests-2.32.4
Some checks are pending
Docs - build & deploy / docs (push) Waiting to run
Run tests / build (ubuntu-latest, 3.10) (push) Waiting to run
Run tests / build (ubuntu-latest, 3.11) (push) Waiting to run
Run tests / build (ubuntu-latest, 3.12) (push) Waiting to run
Run tests / build (ubuntu-latest, 3.13) (push) Waiting to run
Run tests / build (ubuntu-latest, 3.8) (push) Waiting to run
Run tests / build (ubuntu-latest, 3.9) (push) Waiting to run
Run tests / build (windows-latest, 3.10) (push) Waiting to run
Run tests / build (windows-latest, 3.11) (push) Waiting to run
Run tests / build (windows-latest, 3.12) (push) Waiting to run
Run tests / build (windows-latest, 3.13) (push) Waiting to run
Run tests / build (windows-latest, 3.8) (push) Waiting to run
Run tests / build (windows-latest, 3.9) (push) Waiting to run
Run tests / test_docs (3.13) (push) Waiting to run
Run tests / test_sampleproject (3.13) (push) Waiting to run
build(deps): bump requests from 2.32.3 to 2.32.4
2025-06-09 22:42:28 +00:00
dependabot[bot]
291b97cfd1
build(deps): bump requests from 2.32.3 to 2.32.4
Bumps [requests](https://github.com/psf/requests) from 2.32.3 to 2.32.4.
- [Release notes](https://github.com/psf/requests/releases)
- [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md)
- [Commits](https://github.com/psf/requests/compare/v2.32.3...v2.32.4)

---
updated-dependencies:
- dependency-name: requests
  dependency-version: 2.32.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-09 22:28:41 +00:00
github-actions[bot]
6a8fc27bad
Merge pull request #1248 from django-components/dependabot/pip/django-4.2.22
build(deps): bump django from 4.2.21 to 4.2.22
2025-06-09 22:27:01 +00:00
dependabot[bot]
328b55ff29
build(deps): bump django from 4.2.21 to 4.2.22
Bumps [django](https://github.com/django/django) from 4.2.21 to 4.2.22.
- [Commits](https://github.com/django/django/compare/4.2.21...4.2.22)

---
updated-dependencies:
- dependency-name: django
  dependency-version: 4.2.22
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-09 22:18:07 +00:00
github-actions[bot]
136a7a591c
Merge pull request #1246 from django-components/dependabot/pip/zipp-3.23.0
build(deps): bump zipp from 3.22.0 to 3.23.0
2025-06-09 22:16:36 +00:00
dependabot[bot]
4d056b2c49
build(deps): bump zipp from 3.22.0 to 3.23.0
Bumps [zipp](https://github.com/jaraco/zipp) from 3.22.0 to 3.23.0.
- [Release notes](https://github.com/jaraco/zipp/releases)
- [Changelog](https://github.com/jaraco/zipp/blob/main/NEWS.rst)
- [Commits](https://github.com/jaraco/zipp/compare/v3.22.0...v3.23.0)

---
updated-dependencies:
- dependency-name: zipp
  dependency-version: 3.23.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-09 22:05:09 +00:00
github-actions[bot]
8209614fc2
Merge pull request #1247 from django-components/dependabot/pip/mkdocs-git-authors-plugin-0.9.6
build(deps): bump mkdocs-git-authors-plugin from 0.9.5 to 0.9.6
2025-06-09 22:02:12 +00:00
dependabot[bot]
7bece01024
build(deps): bump mkdocs-git-authors-plugin from 0.9.5 to 0.9.6
Bumps [mkdocs-git-authors-plugin](https://github.com/timvink/mkdocs-git-authors-plugin) from 0.9.5 to 0.9.6.
- [Release notes](https://github.com/timvink/mkdocs-git-authors-plugin/releases)
- [Commits](https://github.com/timvink/mkdocs-git-authors-plugin/compare/v0.9.5...v0.9.6)

---
updated-dependencies:
- dependency-name: mkdocs-git-authors-plugin
  dependency-version: 0.9.6
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-09 21:52:53 +00:00
github-actions[bot]
49f0c05bf6
Merge pull request #1244 from django-components/dependabot/pip/mkdocstrings-python-1.16.12
build(deps): bump mkdocstrings-python from 1.16.11 to 1.16.12
2025-06-09 23:51:42 +02:00
dependabot[bot]
831db00598
build(deps): bump mkdocstrings-python from 1.16.11 to 1.16.12
Bumps [mkdocstrings-python](https://github.com/mkdocstrings/python) from 1.16.11 to 1.16.12.
- [Release notes](https://github.com/mkdocstrings/python/releases)
- [Changelog](https://github.com/mkdocstrings/python/blob/main/CHANGELOG.md)
- [Commits](https://github.com/mkdocstrings/python/compare/1.16.11...1.16.12)

---
updated-dependencies:
- dependency-name: mkdocstrings-python
  dependency-version: 1.16.12
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-09 17:14:52 +00:00
Juro Oravec
09bcf8dbcc
feat: on_xx_loaded extension hooks (#1242)
Some checks failed
Run tests / test_sampleproject (3.13) (push) Has been cancelled
Run tests / build (ubuntu-latest, 3.13) (push) Has been cancelled
Run tests / build (ubuntu-latest, 3.8) (push) Has been cancelled
Run tests / build (ubuntu-latest, 3.9) (push) Has been cancelled
Run tests / build (windows-latest, 3.10) (push) Has been cancelled
Run tests / build (windows-latest, 3.11) (push) Has been cancelled
Run tests / build (windows-latest, 3.12) (push) Has been cancelled
Run tests / build (windows-latest, 3.13) (push) Has been cancelled
Run tests / build (windows-latest, 3.8) (push) Has been cancelled
Run tests / build (windows-latest, 3.9) (push) Has been cancelled
Run tests / test_docs (3.13) (push) Has been cancelled
Docs - build & deploy / docs (push) Has been cancelled
Run tests / build (ubuntu-latest, 3.10) (push) Has been cancelled
Run tests / build (ubuntu-latest, 3.11) (push) Has been cancelled
Run tests / build (ubuntu-latest, 3.12) (push) Has been cancelled
* feat: on_xx_loaded extension hooks

* refactor: fix tests
2025-06-08 17:28:10 +02:00
github-actions[bot]
efe5eb0ba5
Merge pull request #1241 from django-components/dependabot/pip/django-4.2.22
build(deps): bump django from 4.2.21 to 4.2.22
2025-06-07 08:39:13 +02:00
dependabot[bot]
ace29db9ac
build(deps): bump django from 4.2.21 to 4.2.22
Bumps [django](https://github.com/django/django) from 4.2.21 to 4.2.22.
- [Commits](https://github.com/django/django/compare/4.2.21...4.2.22)

---
updated-dependencies:
- dependency-name: django
  dependency-version: 4.2.22
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-06 23:26:51 +00:00
Juro Oravec
04737412d9
docs: fix changelog links (#1238) 2025-06-05 08:47:51 +02:00
components-release-bot
b2e053118b Add benchmark results for 0.140.1 2025-06-04 22:42:14 +00:00
Juro Oravec
7b24b86f4a
Update pyproject.toml (#1237)
* Update pyproject.toml

* chore: bump v0.140.1
2025-06-05 00:35:21 +02:00
Juro Oravec
468abaf92f
fix: ctx_with_fills.fill (#1235) 2025-06-05 00:30:23 +02:00
Juro Oravec
593c66db7f
chore: bump v0.140 (#1234) 2025-06-04 23:41:34 +02:00
Juro Oravec
04f79a6e6b
refactor: deprecate Component.input and add raw_args, raw_kwargs, raw_slots (#1233)
* refactor: deprecate Component.input and add raw_args, raw_kwargs, raw_slots

* docs: update changelog
2025-06-04 23:38:50 +02:00
Juro Oravec
eceebb9696
feat: on_render (#1231)
* feat: on_render

* docs: fix typos

* refactor: fix linter errors

* refactor: make `error` in on_render_after optional to fix benchmarks

* refactor: benchmark attempt 2

* refactor: fix linter errors

* refactor: fix formatting
2025-06-04 19:30:03 +02:00
Juro Oravec
46e524e37d
refactor: Add Node metadata (#1229)
* refactor: `Slot.source` replaced with `Slot.fill_node`, new `Component.node` property, and `slot_node` available in `on_slot_rendered()` hook.

* refactor: fix windows path error in tests
2025-06-03 12:58:48 +02:00
dependabot[bot]
abc6be343e
build(deps-dev): bump mypy from 1.15.0 to 1.16.0 (#1226)
Bumps [mypy](https://github.com/python/mypy) from 1.15.0 to 1.16.0.
- [Changelog](https://github.com/python/mypy/blob/master/CHANGELOG.md)
- [Commits](https://github.com/python/mypy/compare/v1.15.0...v1.16.0)

---
updated-dependencies:
- dependency-name: mypy
  dependency-version: 1.16.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-06-03 08:51:34 +02:00
github-actions[bot]
87eb1f8479
Merge pull request #1228 from django-components/dependabot/pip/mkdocs-git-revision-date-localized-plugin-1.4.7
build(deps): bump mkdocs-git-revision-date-localized-plugin from 1.4.6 to 1.4.7
2025-06-03 08:51:03 +02:00
dependabot[bot]
c5a4a81852
build(deps): bump mkdocs-git-revision-date-localized-plugin
Bumps [mkdocs-git-revision-date-localized-plugin](https://github.com/timvink/mkdocs-git-revision-date-localized-plugin) from 1.4.6 to 1.4.7.
- [Release notes](https://github.com/timvink/mkdocs-git-revision-date-localized-plugin/releases)
- [Commits](https://github.com/timvink/mkdocs-git-revision-date-localized-plugin/compare/v1.4.6...v1.4.7)

---
updated-dependencies:
- dependency-name: mkdocs-git-revision-date-localized-plugin
  dependency-version: 1.4.7
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-02 17:40:14 +00:00
Juro Oravec
09cb8714cc
refactor: don't inherit media if child set to None (#1224)
* refactor: don't inherit media if child set to None

* refactor: fix typing errors

* refactor: more type fixes
2025-06-02 16:24:27 +02:00
Juro Oravec
8677ee7941
refactor: deprecate template caching, get_template_name, get_template, assoc template with Comp cls (#1222)
* refactor: deprecate template caching, get_template_name, get_template, assoc template with Comp cls

* refactor: change implementation

* refactor: handle cached template loader

* refactor: fix tests

* refactor: fix test on windows

* refactor: try to  fix type errors

* refactor: Re-cast `context` to fix type errors

* refactor: fix linter error

* refactor: fix typing

* refactor: more linter fixes

* refactor: more linter errors

* refactor: revert extra node metadata
2025-06-01 19:20:22 +02:00
Juro Oravec
fa9ae9892f
feat: Slot.extra and Slot.source metadata (#1221) 2025-05-31 11:22:45 +02:00
Juro Oravec
bb129aefab
feat: extension defaults + docs + API cleanup (#1215) 2025-05-26 23:36:19 +02:00
github-actions[bot]
7df8019544
Merge pull request #1219 from django-components/dependabot/pip/mkdocstrings-python-1.16.11
build(deps): bump mkdocstrings-python from 1.16.10 to 1.16.11
2025-05-26 17:44:44 +00:00
dependabot[bot]
42eb6f93ae
build(deps): bump mkdocstrings-python from 1.16.10 to 1.16.11
Bumps [mkdocstrings-python](https://github.com/mkdocstrings/python) from 1.16.10 to 1.16.11.
- [Release notes](https://github.com/mkdocstrings/python/releases)
- [Changelog](https://github.com/mkdocstrings/python/blob/main/CHANGELOG.md)
- [Commits](https://github.com/mkdocstrings/python/compare/1.16.10...1.16.11)

---
updated-dependencies:
- dependency-name: mkdocstrings-python
  dependency-version: 1.16.11
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-05-26 17:36:58 +00:00
github-actions[bot]
dfb90c4005
Merge pull request #1217 from django-components/dependabot/pip/mkdocs-autorefs-1.4.2
build(deps): bump mkdocs-autorefs from 1.4.1 to 1.4.2
2025-05-26 17:35:48 +00:00
dependabot[bot]
26845fe60a
build(deps): bump mkdocs-autorefs from 1.4.1 to 1.4.2
Bumps [mkdocs-autorefs](https://github.com/mkdocstrings/autorefs) from 1.4.1 to 1.4.2.
- [Release notes](https://github.com/mkdocstrings/autorefs/releases)
- [Changelog](https://github.com/mkdocstrings/autorefs/blob/main/CHANGELOG.md)
- [Commits](https://github.com/mkdocstrings/autorefs/compare/1.4.1...1.4.2)

---
updated-dependencies:
- dependency-name: mkdocs-autorefs
  dependency-version: 1.4.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-05-26 17:28:24 +00:00
github-actions[bot]
e8eb296897
Merge pull request #1218 from django-components/dependabot/pip/zipp-3.22.0
build(deps): bump zipp from 3.21.0 to 3.22.0
2025-05-26 17:27:25 +00:00
dependabot[bot]
6aec2d23dc
build(deps): bump zipp from 3.21.0 to 3.22.0
Bumps [zipp](https://github.com/jaraco/zipp) from 3.21.0 to 3.22.0.
- [Release notes](https://github.com/jaraco/zipp/releases)
- [Changelog](https://github.com/jaraco/zipp/blob/main/NEWS.rst)
- [Commits](https://github.com/jaraco/zipp/compare/v3.21.0...v3.22.0)

---
updated-dependencies:
- dependency-name: zipp
  dependency-version: 3.22.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-05-26 17:17:52 +00:00
github-actions[bot]
7884705869
Merge pull request #1216 from django-components/dependabot/pip/mkdocs-git-revision-date-localized-plugin-1.4.6
build(deps): bump mkdocs-git-revision-date-localized-plugin from 1.4.5 to 1.4.6
2025-05-26 19:16:51 +02:00
dependabot[bot]
d7fee43dd9
build(deps): bump mkdocs-git-revision-date-localized-plugin
Bumps [mkdocs-git-revision-date-localized-plugin](https://github.com/timvink/mkdocs-git-revision-date-localized-plugin) from 1.4.5 to 1.4.6.
- [Release notes](https://github.com/timvink/mkdocs-git-revision-date-localized-plugin/releases)
- [Commits](https://github.com/timvink/mkdocs-git-revision-date-localized-plugin/compare/v1.4.5...v1.4.6)

---
updated-dependencies:
- dependency-name: mkdocs-git-revision-date-localized-plugin
  dependency-version: 1.4.6
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-05-26 17:05:33 +00:00
Juro Oravec
55b1c8bc62
refecator: move defaults applying back to ext, raise on passing Slot to Slot, and docs cleanup (#1214)
* refecator: move defaults applying back to ext, raise on passing Slot to Slot, and docs cleanup

* docs: fix typo
2025-05-26 11:59:17 +02:00
Juro Oravec
bae0f28813
refactor: Instantiate component when rendering, and remove metadata stack (#1212)
* refactor: Instantiate component when rendering, and remove metadata stack

* refactor: update test

* refactor: fix linter errors

* docs: remove example from changelog
2025-05-25 23:33:38 +02:00
Juro Oravec
2e08af9a13
refactor: move slot escaping inside Slot, remve Slot.escaped, and remove render kwarg escape_slots_content (#1211) 2025-05-25 17:24:45 +02:00
Juro Oravec
046569e16d
fix: KeyError on component_context_cache when slot rendered outside (#1210) 2025-05-25 11:58:17 +02:00
Juro Oravec
6ff2d78a2f
feat: on_slot_rendered extension hook + refactor debug highlight as extension (#1209)
* feat: on_slot_rendered extension hook + refactor debug highlight as extension

* refactor: fix whitespace in test output
2025-05-25 11:20:32 +02:00
Juro Oravec
223fc2c68c
docs: update docs on slots (#1208) 2025-05-25 08:53:09 +02:00
Juro Oravec
e054a68715
feat: Component.args/kwargs/slots and {{ component_vars.args/kwargs/s… (#1205)
* feat: Component.args/kwargs/slots and {{ component_vars.args/kwargs/slots }}

* docs: fix typo in changelog
2025-05-24 23:24:34 +02:00
Juro Oravec
d514694788
feat: Pass Slots to {% fill %} with 'body' kwarg (#1203) 2025-05-22 08:01:21 +02:00
Juro Oravec
f069255b64
refactor: simplify slot API (#1202)
Closes #1096
2025-05-20 09:48:45 +02:00
dependabot[bot]
7a49a7806c
build(deps): bump cairosvg from 2.8.0 to 2.8.2 (#1201)
Bumps [cairosvg](https://github.com/Kozea/CairoSVG) from 2.8.0 to 2.8.2.
- [Release notes](https://github.com/Kozea/CairoSVG/releases)
- [Changelog](https://github.com/Kozea/CairoSVG/blob/main/NEWS.rst)
- [Commits](https://github.com/Kozea/CairoSVG/compare/2.8.0...2.8.2)

---
updated-dependencies:
- dependency-name: cairosvg
  dependency-version: 2.8.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Juro Oravec <juraj.oravec.josefson@gmail.com>
2025-05-19 20:43:39 +02:00
github-actions[bot]
0718b4cac6
Merge pull request #1200 from django-components/dependabot/pip/pyyaml-env-tag-1.1
build(deps): bump pyyaml-env-tag from 1.0 to 1.1
2025-05-19 18:23:53 +00:00
Juro Oravec
4f15ad8360
Merge branch 'master' into dependabot/pip/pyyaml-env-tag-1.1 2025-05-19 20:09:31 +02:00
Juro Oravec
49ad23b21d
refactor: slots cleanup (#1199) 2025-05-19 19:56:27 +02:00
dependabot[bot]
71db46d5ca
build(deps): bump pyyaml-env-tag from 1.0 to 1.1
Bumps [pyyaml-env-tag](https://github.com/waylan/pyyaml-env-tag) from 1.0 to 1.1.
- [Commits](https://github.com/waylan/pyyaml-env-tag/compare/1.0...1.1)

---
updated-dependencies:
- dependency-name: pyyaml-env-tag
  dependency-version: '1.1'
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-05-19 17:47:21 +00:00
Juro Oravec
79c42da2f9
feat: slot caching (#1196)
* feat: slot caching

Closes #1164

* refactor: fix linter
2025-05-19 19:26:57 +02:00
Juro Oravec
b6b574d875
refactor: Rename {% fill default=... %} to {% fill fallback=... %} (#1190) 2025-05-19 19:05:39 +02:00
Juro Oravec
0d05ef4cb2
feat: Expose slot input as Slot.contents (#1180)
* feat: expose slot input as Slot.contents

* refactor: fix linter errors
2025-05-14 11:17:09 +02:00
github-actions[bot]
53d80684bb
Merge pull request #1181 from django-components/dependabot/pip/mkdocs-material-9.6.13
build(deps): bump mkdocs-material from 9.6.12 to 9.6.13
2025-05-14 09:15:37 +00:00
Juro Oravec
2eb9f5d43d
Merge branch 'master' into dependabot/pip/mkdocs-material-9.6.13 2025-05-14 11:05:48 +02:00
Juro Oravec
1bb7c3fdfe
Update requirements-docs.txt 2025-05-14 11:05:36 +02:00
Juro Oravec
f80457e964
Update attributes.py (#1188) 2025-05-13 23:18:48 +02:00
github-actions[bot]
a96f5fdd2f
Merge pull request #1182 from django-components/dependabot/pip/platformdirs-4.3.8
build(deps): bump platformdirs from 4.3.7 to 4.3.8
2025-05-13 11:53:36 +02:00
Juro Oravec
b83eaefcd0
Merge branch 'master' into dependabot/pip/mkdocs-material-9.6.13 2025-05-13 09:24:40 +02:00
Juro Oravec
9dda6f8c16
Merge branch 'master' into dependabot/pip/platformdirs-4.3.8 2025-05-13 09:24:30 +02:00
dependabot[bot]
5fb0c9032b
build(deps): bump pyyaml-env-tag from 0.1 to 1.0 (#1183)
Bumps [pyyaml-env-tag](https://github.com/waylan/pyyaml-env-tag) from 0.1 to 1.0.
- [Commits](https://github.com/waylan/pyyaml-env-tag/compare/0.1...1.0)

---
updated-dependencies:
- dependency-name: pyyaml-env-tag
  dependency-version: '1.0'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-05-13 09:24:11 +02:00
dependabot[bot]
e5126b04ef
build(deps): bump platformdirs from 4.3.7 to 4.3.8
Bumps [platformdirs](https://github.com/tox-dev/platformdirs) from 4.3.7 to 4.3.8.
- [Release notes](https://github.com/tox-dev/platformdirs/releases)
- [Changelog](https://github.com/tox-dev/platformdirs/blob/main/CHANGES.rst)
- [Commits](https://github.com/tox-dev/platformdirs/compare/4.3.7...4.3.8)

---
updated-dependencies:
- dependency-name: platformdirs
  dependency-version: 4.3.8
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-05-13 07:23:59 +00:00
Juro Oravec
77178190ed
Merge branch 'master' into dependabot/pip/mkdocs-material-9.6.13 2025-05-13 09:23:40 +02:00
github-actions[bot]
d7e6d5a961
Merge pull request #1184 from django-components/dependabot/pip/virtualenv-20.31.2
build(deps): bump virtualenv from 20.30 to 20.31.2
2025-05-13 09:22:51 +02:00
dependabot[bot]
406828c51a
build(deps): bump virtualenv from 20.30 to 20.31.2
Bumps [virtualenv](https://github.com/pypa/virtualenv) from 20.30 to 20.31.2.
- [Release notes](https://github.com/pypa/virtualenv/releases)
- [Changelog](https://github.com/pypa/virtualenv/blob/main/docs/changelog.rst)
- [Commits](https://github.com/pypa/virtualenv/compare/20.30.0...20.31.2)

---
updated-dependencies:
- dependency-name: virtualenv
  dependency-version: 20.31.2
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-05-12 17:14:24 +00:00
dependabot[bot]
fcc8d71dca
build(deps): bump mkdocs-material from 9.6.12 to 9.6.13
Bumps [mkdocs-material](https://github.com/squidfunk/mkdocs-material) from 9.6.12 to 9.6.13.
- [Release notes](https://github.com/squidfunk/mkdocs-material/releases)
- [Changelog](https://github.com/squidfunk/mkdocs-material/blob/master/CHANGELOG)
- [Commits](https://github.com/squidfunk/mkdocs-material/compare/9.6.12...9.6.13)

---
updated-dependencies:
- dependency-name: mkdocs-material
  dependency-version: 9.6.13
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-05-12 17:12:51 +00:00
Juro Oravec
ccf02fa316
chore: util to manage URLs in the codebase (#1179)
* chore: util to manage URLs in the codebase

* docs: mentiion validate_links and supported_versions in docs

* refactor: fix linter errors
2025-05-11 14:59:34 +02:00
Juro Oravec
5f4fbe76e5
feat: add BaseNode.contents (#1177) 2025-05-11 08:11:07 +02:00
Juro Oravec
661413d4a9
refactor: change caching methods to accept slots + typing fixes (#1173) 2025-05-09 10:19:34 +02:00
github-actions[bot]
e64cd197c1
Merge pull request #1176 from django-components/dependabot/pip/django-4.2.21
build(deps): bump django from 4.2.20 to 4.2.21
2025-05-09 09:41:08 +02:00
dependabot[bot]
ccd8e26956
build(deps): bump django from 4.2.20 to 4.2.21
Bumps [django](https://github.com/django/django) from 4.2.20 to 4.2.21.
- [Commits](https://github.com/django/django/compare/4.2.20...4.2.21)

---
updated-dependencies:
- dependency-name: django
  dependency-version: 4.2.21
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-05-09 06:51:25 +00:00
github-actions[bot]
6aa2a39fe9
Merge pull request #1175 from django-components/dependabot/pip/django-4.2.21
build(deps): bump django from 4.2.20 to 4.2.21
2025-05-09 08:50:10 +02:00
dependabot[bot]
26078d5340
build(deps): bump django from 4.2.20 to 4.2.21
Bumps [django](https://github.com/django/django) from 4.2.20 to 4.2.21.
- [Commits](https://github.com/django/django/compare/4.2.20...4.2.21)

---
updated-dependencies:
- dependency-name: django
  dependency-version: 4.2.21
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-05-08 15:27:54 +00:00
Juro Oravec
2dacac1f43
temp (#1172) 2025-05-06 21:48:18 +02:00
Juro Oravec
6253042e9e
refactor: remove middleware, add strategy "raw", and call render_deps() from within Template.render() (#1166)
* refactor: remove middleware, add strategy "raw", and call render_deps() from within Template.render()

* refactor: fix formatting

* refactor: fix benchmark tests

* refactor: avoid processing deps if rendered HTML contains no components

* refactor: remove comments

* refactor: rename "raw" to "ignore"
2025-05-06 21:36:41 +02:00
github-actions[bot]
1049c08324
Merge pull request #1167 from django-components/dependabot/pip/charset-normalizer-3.4.2
build(deps): bump charset-normalizer from 3.4.1 to 3.4.2
2025-05-06 13:11:30 +02:00
Juro Oravec
ca4e7f7f8b refactor: pin virtualenv to v20.30 to fix asv 2025-05-06 10:09:32 +00:00
dependabot[bot]
b13f859d7b
build(deps): bump charset-normalizer from 3.4.1 to 3.4.2
Bumps [charset-normalizer](https://github.com/jawah/charset_normalizer) from 3.4.1 to 3.4.2.
- [Release notes](https://github.com/jawah/charset_normalizer/releases)
- [Changelog](https://github.com/jawah/charset_normalizer/blob/master/CHANGELOG.md)
- [Commits](https://github.com/jawah/charset_normalizer/compare/3.4.1...3.4.2)

---
updated-dependencies:
- dependency-name: charset-normalizer
  dependency-version: 3.4.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-05-05 18:07:36 +00:00
Juro Oravec
330578a2c7
refactor: update impl of format_url (#1163) 2025-05-04 12:24:04 +02:00
Juro Oravec
d4d834256a
refactor: rename context_data field to template_data (#1162) 2025-05-04 01:49:54 +02:00
Juro Oravec
28b61c1609
refactor: Update docs and tests to use get_template_data() (#1161)
* refactor: update docs and tests to use get_template_data()

* refactor: fix linting

* docs: add note about difference between the two methods
2025-05-03 12:04:10 +02:00
Juro Oravec
c69980493d
feat: allow to set query and fragment on get_component_url (#1160) 2025-05-03 10:29:38 +02:00
Juro Oravec
bf7a204e92
feat: add "simple", "prepend", and "append" render types (#1156)
* feat: add "simple", "prepend", and "append" render types

* refactor: explicitly set strategy for "document" in tests
2025-05-02 15:07:16 +02:00
github-actions[bot]
e74e1241ac
Merge pull request #1158 from django-components/dependabot/github_actions/actions/create-github-app-token-2
build(deps): bump actions/create-github-app-token from 1 to 2
2025-05-01 20:54:55 +02:00
dependabot[bot]
76a888aa11
build(deps): bump actions/create-github-app-token from 1 to 2
Bumps [actions/create-github-app-token](https://github.com/actions/create-github-app-token) from 1 to 2.
- [Release notes](https://github.com/actions/create-github-app-token/releases)
- [Commits](https://github.com/actions/create-github-app-token/compare/v1...v2)

---
updated-dependencies:
- dependency-name: actions/create-github-app-token
  dependency-version: '2'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-05-01 17:31:27 +00:00
github-actions[bot]
bb9e8e0bad
Merge pull request #1155 from django-components/dependabot/pip/mypy-extensions-1.1.0
build(deps): bump mypy-extensions from 1.0.0 to 1.1.0
2025-04-28 21:21:52 +00:00
Juro Oravec
95e5df9d3c
Merge branch 'master' into dependabot/pip/mypy-extensions-1.1.0 2025-04-28 23:15:27 +02:00
dependabot[bot]
c5c7b7a52c
build(deps): bump mypy-extensions from 1.0.0 to 1.1.0
Bumps [mypy-extensions](https://github.com/python/mypy_extensions) from 1.0.0 to 1.1.0.
- [Commits](https://github.com/python/mypy_extensions/compare/1.0.0...1.1.0)

---
updated-dependencies:
- dependency-name: mypy-extensions
  dependency-version: 1.1.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-28 18:48:11 +00:00
dependabot[bot]
3eb148754a
build(deps): bump pymdown-extensions from 10.14.3 to 10.15 (#1151)
Bumps [pymdown-extensions](https://github.com/facelessuser/pymdown-extensions) from 10.14.3 to 10.15.
- [Release notes](https://github.com/facelessuser/pymdown-extensions/releases)
- [Commits](https://github.com/facelessuser/pymdown-extensions/compare/10.14.3...10.15)

---
updated-dependencies:
- dependency-name: pymdown-extensions
  dependency-version: '10.15'
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-04-28 20:15:37 +02:00
dependabot[bot]
b8dbf89612
build(deps): bump importlib-metadata from 8.6.1 to 8.7.0 (#1150)
Bumps [importlib-metadata](https://github.com/python/importlib_metadata) from 8.6.1 to 8.7.0.
- [Release notes](https://github.com/python/importlib_metadata/releases)
- [Changelog](https://github.com/python/importlib_metadata/blob/main/NEWS.rst)
- [Commits](https://github.com/python/importlib_metadata/compare/v8.6.1...v8.7.0)

---
updated-dependencies:
- dependency-name: importlib-metadata
  dependency-version: 8.7.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-04-28 20:15:08 +02:00
dependabot[bot]
d33a1b87aa
build(deps): bump certifi from 2025.1.31 to 2025.4.26 (#1153)
Bumps [certifi](https://github.com/certifi/python-certifi) from 2025.1.31 to 2025.4.26.
- [Commits](https://github.com/certifi/python-certifi/compare/2025.01.31...2025.04.26)

---
updated-dependencies:
- dependency-name: certifi
  dependency-version: 2025.4.26
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-04-28 20:14:42 +02:00
dependabot[bot]
456e011f9b
build(deps): bump mkdocs-git-authors-plugin from 0.9.4 to 0.9.5 (#1154)
Bumps [mkdocs-git-authors-plugin](https://github.com/timvink/mkdocs-git-authors-plugin) from 0.9.4 to 0.9.5.
- [Release notes](https://github.com/timvink/mkdocs-git-authors-plugin/releases)
- [Commits](https://github.com/timvink/mkdocs-git-authors-plugin/compare/v0.9.4...v0.9.5)

---
updated-dependencies:
- dependency-name: mkdocs-git-authors-plugin
  dependency-version: 0.9.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-04-28 20:14:15 +02:00
github-actions[bot]
6a452b285e
Merge pull request #1152 from django-components/dependabot/pip/griffe-1.7.3
build(deps): bump griffe from 1.7.2 to 1.7.3
2025-04-28 20:08:33 +02:00
dependabot[bot]
7b15ea9ea8
build(deps): bump griffe from 1.7.2 to 1.7.3
Bumps [griffe](https://github.com/mkdocstrings/griffe) from 1.7.2 to 1.7.3.
- [Release notes](https://github.com/mkdocstrings/griffe/releases)
- [Changelog](https://github.com/mkdocstrings/griffe/blob/main/CHANGELOG.md)
- [Commits](https://github.com/mkdocstrings/griffe/compare/1.7.2...1.7.3)

---
updated-dependencies:
- dependency-name: griffe
  dependency-version: 1.7.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-28 17:54:33 +00:00
Juro Oravec
59f82307ae
docs: docstrings, fundamentals, and minor changes (#1145)
* docs: docstrings, fundamentals, and minor changes

* refactor: fix tests + linter errors
2025-04-24 12:47:04 +02:00
Ralph Bibera
89db10a643
fixed README Quickstart file extension typo (#1146) 2025-04-24 09:21:08 +02:00
github-actions[bot]
5704bc86a1
Merge pull request #1142 from django-components/dependabot/pip/mkdocs-material-9.6.12
build(deps): bump mkdocs-material from 9.6.11 to 9.6.12
2025-04-22 07:16:11 +00:00
Juro Oravec
a5a93e9bbb
Merge branch 'master' into dependabot/pip/mkdocs-material-9.6.12 2025-04-22 09:08:07 +02:00
github-actions[bot]
a368786267
Merge pull request #1143 from django-components/dependabot/pip/packaging-25.0
build(deps): bump packaging from 24.2 to 25.0
2025-04-21 21:23:09 +00:00
Juro Oravec
dbe8dc074d
Merge branch 'master' into dependabot/pip/mkdocs-material-9.6.12 2025-04-21 23:13:16 +02:00
Juro Oravec
2b69980845
Merge branch 'master' into dependabot/pip/packaging-25.0 2025-04-21 23:12:57 +02:00
Juro Oravec
519529d4e4
refactor: move Url.public to View.public (#1140)
* refactor: move Url.public to View.public

* refactor: fix tests / imports
2025-04-21 23:12:40 +02:00
dependabot[bot]
c8002d241a
build(deps): bump packaging from 24.2 to 25.0
Bumps [packaging](https://github.com/pypa/packaging) from 24.2 to 25.0.
- [Release notes](https://github.com/pypa/packaging/releases)
- [Changelog](https://github.com/pypa/packaging/blob/main/CHANGELOG.rst)
- [Commits](https://github.com/pypa/packaging/compare/24.2...25.0)

---
updated-dependencies:
- dependency-name: packaging
  dependency-version: '25.0'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-21 18:00:44 +00:00
dependabot[bot]
b949e4a6ec
build(deps): bump mkdocs-material from 9.6.11 to 9.6.12
Bumps [mkdocs-material](https://github.com/squidfunk/mkdocs-material) from 9.6.11 to 9.6.12.
- [Release notes](https://github.com/squidfunk/mkdocs-material/releases)
- [Changelog](https://github.com/squidfunk/mkdocs-material/blob/master/CHANGELOG)
- [Commits](https://github.com/squidfunk/mkdocs-material/compare/9.6.11...9.6.12)

---
updated-dependencies:
- dependency-name: mkdocs-material
  dependency-version: 9.6.12
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-21 18:00:19 +00:00
Juro Oravec
b49002b545
refactor: change component typing from generics to class attributes (#1138) 2025-04-20 22:05:29 +02:00
components-release-bot
912d8e8074 Add benchmark results for 0.139.1 2025-04-20 10:07:16 +00:00
Juro Oravec
4c90948606
chore: bump v0.139.1 (#1139) 2025-04-20 11:58:12 +02:00
Oliver Haas
e0b718c314
test: test to illustrate bug in component caching when using include tag (#1135)
* test: test to illustrate bug in component caching when using include tag

* fix: add cleanup for render context in component rendering

* refactor: clarify cleanup comment in component rendering logic

* refactor: fix linter errors

* test: formatting and unnecessary test setup

---------

Co-authored-by: Juro Oravec <juraj.oravec.josefson@gmail.com>
2025-04-20 11:53:06 +02:00
Juro Oravec
eed15d32ab
Update tests.yml (#1137) 2025-04-20 00:02:28 +02:00
dependabot[bot]
68ede4f662
build(deps): bump pillow from 11.1.0 to 11.2.1 (#1129)
Bumps [pillow](https://github.com/python-pillow/Pillow) from 11.1.0 to 11.2.1.
- [Release notes](https://github.com/python-pillow/Pillow/releases)
- [Changelog](https://github.com/python-pillow/Pillow/blob/main/CHANGES.rst)
- [Commits](https://github.com/python-pillow/Pillow/compare/11.1.0...11.2.1)

---
updated-dependencies:
- dependency-name: pillow
  dependency-version: 11.2.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-04-14 20:40:43 +02:00
dependabot[bot]
bd1a4fd65d
build(deps): bump typing-extensions from 4.12.2 to 4.13.2 (#1131)
Bumps [typing-extensions](https://github.com/python/typing_extensions) from 4.12.2 to 4.13.2.
- [Release notes](https://github.com/python/typing_extensions/releases)
- [Changelog](https://github.com/python/typing_extensions/blob/main/CHANGELOG.md)
- [Commits](https://github.com/python/typing_extensions/compare/4.12.2...4.13.2)

---
updated-dependencies:
- dependency-name: typing-extensions
  dependency-version: 4.13.2
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-04-14 20:39:46 +02:00
github-actions[bot]
56846c5fc4
Merge pull request #1128 from django-components/dependabot/pip/markdown-3.8
build(deps): bump markdown from 3.7 to 3.8
2025-04-14 18:35:38 +00:00
dependabot[bot]
8cf42dd20f
build(deps): bump markdown from 3.7 to 3.8
Bumps [markdown](https://github.com/Python-Markdown/markdown) from 3.7 to 3.8.
- [Release notes](https://github.com/Python-Markdown/markdown/releases)
- [Changelog](https://github.com/Python-Markdown/markdown/blob/master/docs/changelog.md)
- [Commits](https://github.com/Python-Markdown/markdown/compare/3.7...3.8)

---
updated-dependencies:
- dependency-name: markdown
  dependency-version: '3.8'
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-14 18:26:16 +00:00
github-actions[bot]
2905c6980a
Merge pull request #1130 from django-components/dependabot/pip/urllib3-2.4.0
build(deps): bump urllib3 from 2.3.0 to 2.4.0
2025-04-14 18:25:07 +00:00
dependabot[bot]
0fad34f271
build(deps): bump urllib3 from 2.3.0 to 2.4.0
Bumps [urllib3](https://github.com/urllib3/urllib3) from 2.3.0 to 2.4.0.
- [Release notes](https://github.com/urllib3/urllib3/releases)
- [Changelog](https://github.com/urllib3/urllib3/blob/main/CHANGES.rst)
- [Commits](https://github.com/urllib3/urllib3/compare/2.3.0...2.4.0)

---
updated-dependencies:
- dependency-name: urllib3
  dependency-version: 2.4.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-14 18:16:17 +00:00
github-actions[bot]
6e7e5cd162
Merge pull request #1132 from django-components/dependabot/pip/pytz-2025.2
build(deps): bump pytz from 2025.1 to 2025.2
2025-04-14 20:15:18 +02:00
dependabot[bot]
a69c2329b0
build(deps): bump pytz from 2025.1 to 2025.2
Bumps [pytz](https://github.com/stub42/pytz) from 2025.1 to 2025.2.
- [Release notes](https://github.com/stub42/pytz/releases)
- [Commits](https://github.com/stub42/pytz/compare/release_2025.1...release_2025.2)

---
updated-dependencies:
- dependency-name: pytz
  dependency-version: '2025.2'
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-14 17:43:21 +00:00
Juro Oravec
c650e7f3a5
refactor: prefix component ID with c (#1127) 2025-04-14 12:01:16 +02:00
Juro Oravec
fc026cbd99
docs: update section on working with request object (#1126)
* docs: update section on working with request object

* refactor: fix linting
2025-04-14 11:35:48 +02:00
Juro Oravec
06cad2ec64
refactor: use typevar defaults + raise on conflicting extensions (#1125)
* refactor: use typevar defaults + raise on conflicting extensions

* refactor: fix linter errors
2025-04-14 10:00:18 +02:00
Juro Oravec
61528ef0ad
build: Change versioning to major.minor.patch (#1123) 2025-04-12 20:48:54 +02:00
components-release-bot
3148557e91 Add benchmark results for 0.139 2025-04-12 07:41:59 +00:00
Juro Oravec
ad402fc619
refactor: fix compat with Django 5.2 Finder.find() (#1121)
* refactor: fix compat with Django 5.2 Finder.find()

* refactor: fix tests and linters
2025-04-12 09:35:33 +02:00
Juro Oravec
d37291a3b6
docs: document how to access component instance from within View (#1115) 2025-04-10 09:39:52 +02:00
components-release-bot
5b01d6c3c6 Add benchmark results for 0.138 2025-04-09 16:37:58 +00:00
Juro Oravec
07f747d705
refactor: fix - allow components with Url.public to be defined before django.setup() (#1112) 2025-04-09 18:31:07 +02:00
components-release-bot
cc249022c4 Add benchmark results for 0.137 2025-04-09 13:22:30 +00:00
Juro Oravec
1319a95627
chore: bump v0.137 (#1111) 2025-04-09 15:09:26 +02:00
Juro Oravec
613dfea379
refactor: cleanup docs, add docs on Render API, allow get_context_data return None (#1110)
* refactor: cleanup docs, add docs on Render API, allow get_context_data return None

* refactor: fix linter and tests
2025-04-09 15:06:14 +02:00
Juro Oravec
9ede779fa3
docs: update testing, single file comp, and syntax highlight (#1109) 2025-04-08 14:32:23 +02:00
Juro Oravec
b6994e9ad3
feat: component caching (#1097)
* feat: allow to set defaults

* refactor: remove input validation and link to it

* feat: component URL

* feat: component caching

* refactor: Mark `OnComponentRenderedContext` as extension hook for docs

* docs: update changelog

* refactor: simplify hash methods
2025-04-08 11:54:39 +02:00
github-actions[bot]
ef15117459
Merge pull request #1108 from django-components/dependabot/pip/mkdocs-material-9.6.11
build(deps): bump mkdocs-material from 9.6.10 to 9.6.11
2025-04-07 20:59:59 +00:00
Emil Stenström
625c1a4735
Merge branch 'master' into dependabot/pip/mkdocs-material-9.6.11 2025-04-07 22:52:29 +02:00
dependabot[bot]
c8f9b35f2e
build(deps): bump mkdocs-material from 9.6.10 to 9.6.11
Bumps [mkdocs-material](https://github.com/squidfunk/mkdocs-material) from 9.6.10 to 9.6.11.
- [Release notes](https://github.com/squidfunk/mkdocs-material/releases)
- [Changelog](https://github.com/squidfunk/mkdocs-material/blob/master/CHANGELOG)
- [Commits](https://github.com/squidfunk/mkdocs-material/compare/9.6.10...9.6.11)

---
updated-dependencies:
- dependency-name: mkdocs-material
  dependency-version: 9.6.11
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-07 20:06:09 +00:00
dependabot[bot]
077ba59ce1
build(deps): bump pytest-django from 4.10.0 to 4.11.1 (#1102)
Bumps [pytest-django](https://github.com/pytest-dev/pytest-django) from 4.10.0 to 4.11.1.
- [Release notes](https://github.com/pytest-dev/pytest-django/releases)
- [Changelog](https://github.com/pytest-dev/pytest-django/blob/main/docs/changelog.rst)
- [Commits](https://github.com/pytest-dev/pytest-django/compare/v4.10.0...v4.11.1)

---
updated-dependencies:
- dependency-name: pytest-django
  dependency-version: 4.11.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Juro Oravec <juraj.oravec.josefson@gmail.com>
2025-04-07 22:02:09 +02:00
github-actions[bot]
0417e369f7
Merge pull request #1106 from django-components/dependabot/pip/pyparsing-3.2.3
build(deps): bump pyparsing from 3.2.2 to 3.2.3
2025-04-07 19:47:31 +00:00
Juro Oravec
2213f43c89
Merge branch 'master' into dependabot/pip/pyparsing-3.2.3 2025-04-07 21:36:48 +02:00
github-actions[bot]
1074ebe2f1
Merge pull request #1103 from django-components/dependabot/pip/mkdocstrings-python-1.16.10
build(deps): bump mkdocstrings-python from 1.16.8 to 1.16.10
2025-04-07 21:36:26 +02:00
Juro Oravec
225f45d0b8
Merge branch 'master' into dependabot/pip/pyparsing-3.2.3 2025-04-07 21:36:21 +02:00
dependabot[bot]
ec5cc278a4
build(deps): bump pyparsing from 3.2.2 to 3.2.3
Bumps [pyparsing](https://github.com/pyparsing/pyparsing) from 3.2.2 to 3.2.3.
- [Release notes](https://github.com/pyparsing/pyparsing/releases)
- [Changelog](https://github.com/pyparsing/pyparsing/blob/master/CHANGES)
- [Commits](https://github.com/pyparsing/pyparsing/compare/3.2.2...3.2.3)

---
updated-dependencies:
- dependency-name: pyparsing
  dependency-version: 3.2.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-07 19:12:42 +00:00
dependabot[bot]
e0e9501dca
build(deps): bump mkdocstrings-python from 1.16.8 to 1.16.10
Bumps [mkdocstrings-python](https://github.com/mkdocstrings/python) from 1.16.8 to 1.16.10.
- [Release notes](https://github.com/mkdocstrings/python/releases)
- [Changelog](https://github.com/mkdocstrings/python/blob/main/CHANGELOG.md)
- [Commits](https://github.com/mkdocstrings/python/compare/1.16.8...1.16.10)

---
updated-dependencies:
- dependency-name: mkdocstrings-python
  dependency-version: 1.16.10
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-07 18:32:24 +00:00
github-actions[bot]
fd12bb47ab
Merge pull request #1104 from django-components/dependabot/pip/griffe-1.7.2
build(deps): bump griffe from 1.7.1 to 1.7.2
2025-04-07 18:31:13 +00:00
Juro Oravec
bd703ead4b
Merge branch 'master' into dependabot/pip/griffe-1.7.2 2025-04-07 20:21:49 +02:00
github-actions[bot]
084b6c7f34
Merge pull request #1100 from django-components/dependabot/pip/flake8-7.2.0
build(deps-dev): bump flake8 from 7.1.2 to 7.2.0
2025-04-07 20:21:21 +02:00
Juro Oravec
dfb675343e
Merge branch 'master' into dependabot/pip/flake8-7.2.0 2025-04-07 20:04:57 +02:00
dependabot[bot]
2fedd9d0e2
build(deps): bump griffe from 1.7.1 to 1.7.2
Bumps [griffe](https://github.com/mkdocstrings/griffe) from 1.7.1 to 1.7.2.
- [Release notes](https://github.com/mkdocstrings/griffe/releases)
- [Changelog](https://github.com/mkdocstrings/griffe/blob/main/CHANGELOG.md)
- [Commits](https://github.com/mkdocstrings/griffe/compare/1.7.1...1.7.2)

---
updated-dependencies:
- dependency-name: griffe
  dependency-version: 1.7.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-07 18:03:26 +00:00
dependabot[bot]
db324e291f
build(deps-dev): bump flake8 from 7.1.2 to 7.2.0
Bumps [flake8](https://github.com/pycqa/flake8) from 7.1.2 to 7.2.0.
- [Commits](https://github.com/pycqa/flake8/compare/7.1.2...7.2.0)

---
updated-dependencies:
- dependency-name: flake8
  dependency-version: 7.2.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-07 17:53:36 +00:00
Juro Oravec
1dfec8fc6d
docs: mention URL auto-gen in README (#1099) 2025-04-07 19:26:50 +02:00
Juro Oravec
9f9b1f7232
refactor: fix link in docs + call django.setup() in @djc_test if not done so (#1098) 2025-04-07 15:07:46 +02:00
Juro Oravec
bb5de86b69
feat: expose _class_hash as class_id (#1094)
* feat: expose _class_hash as class_id

* refactor: fix linting
2025-04-07 11:08:02 +02:00
Juro Oravec
a49f5e51dd
feat: component URL (#1088)
* feat: allow to set defaults

* refactor: remove input validation and link to it

* feat: component URL

* refactor: fix linter errors

* refactor: fix linter errors + update examples to use Component.View..get

* docs: update comment

* refactor: revert change to hash_comp_cls

* docs: update comment
2025-04-07 10:44:41 +02:00
Juro Oravec
3555411f1e
docs: move extension command- and url-related API to own API pages (#1093)
* docs: move extension command- and url-related API to own API pages

* refactor: fix linters
2025-04-06 14:12:15 +02:00
Juro Oravec
0ed46e4d30
chore: add support for Django 5.2 and drop for 5.0 (#1092) 2025-04-06 11:57:41 +02:00
components-release-bot
637e143538 Add benchmark results for 0.136 2025-04-05 07:18:52 +00:00
Juro Oravec
fdd29baa65
chore: bump v0.136 (#1087) 2025-04-05 09:10:55 +02:00
Juro Oravec
2499126d1f
refactor: fix ext URLs lookup (#1086)
* refactor: fix ext URLs lookup
2025-04-05 09:10:04 +02:00
Juro Oravec
7e74831599
refactor: remove input validation and link to it (#1082)
* feat: allow to set defaults

* refactor: remove input validation and link to it

* docs: update changelog

* Update typing_and_validation.md

* Update typing_and_validation.md
2025-04-05 08:19:19 +02:00
github-actions[bot]
5e263ec143
Merge pull request #1078 from django-components/dependabot/pip/markdown-exec-1.10.3
build(deps): bump markdown-exec from 1.10.2 to 1.10.3
2025-03-31 20:09:25 +00:00
dependabot[bot]
ce140ae2c2
build(deps): bump markdown-exec from 1.10.2 to 1.10.3
Bumps [markdown-exec](https://github.com/pawamoy/markdown-exec) from 1.10.2 to 1.10.3.
- [Release notes](https://github.com/pawamoy/markdown-exec/releases)
- [Changelog](https://github.com/pawamoy/markdown-exec/blob/main/CHANGELOG.md)
- [Commits](https://github.com/pawamoy/markdown-exec/compare/1.10.2...1.10.3)

---
updated-dependencies:
- dependency-name: markdown-exec
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-31 20:03:11 +00:00
github-actions[bot]
57d035c67d
Merge pull request #1080 from django-components/dependabot/pip/mkdocs-material-9.6.10
build(deps): bump mkdocs-material from 9.6.9 to 9.6.10
2025-03-31 20:01:57 +00:00
dependabot[bot]
1b8122312e
build(deps): bump mkdocs-material from 9.6.9 to 9.6.10
Bumps [mkdocs-material](https://github.com/squidfunk/mkdocs-material) from 9.6.9 to 9.6.10.
- [Release notes](https://github.com/squidfunk/mkdocs-material/releases)
- [Changelog](https://github.com/squidfunk/mkdocs-material/blob/master/CHANGELOG)
- [Commits](https://github.com/squidfunk/mkdocs-material/compare/9.6.9...9.6.10)

---
updated-dependencies:
- dependency-name: mkdocs-material
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-31 19:55:36 +00:00
github-actions[bot]
e184834e93
Merge pull request #1077 from django-components/dependabot/pip/mkdocstrings-0.29.1
build(deps): bump mkdocstrings from 0.29.0 to 0.29.1
2025-03-31 19:54:24 +00:00
dependabot[bot]
d523a97fcc
build(deps): bump mkdocstrings from 0.29.0 to 0.29.1
Bumps [mkdocstrings](https://github.com/mkdocstrings/mkdocstrings) from 0.29.0 to 0.29.1.
- [Release notes](https://github.com/mkdocstrings/mkdocstrings/releases)
- [Changelog](https://github.com/mkdocstrings/mkdocstrings/blob/main/CHANGELOG.md)
- [Commits](https://github.com/mkdocstrings/mkdocstrings/compare/0.29.0...0.29.1)

---
updated-dependencies:
- dependency-name: mkdocstrings
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-31 19:43:42 +00:00
github-actions[bot]
61a70e7705
Merge pull request #1079 from django-components/dependabot/pip/griffe-1.7.1
build(deps): bump griffe from 1.6.2 to 1.7.1
2025-03-31 19:42:34 +00:00
dependabot[bot]
e35c4e24fb
build(deps): bump griffe from 1.6.2 to 1.7.1
Bumps [griffe](https://github.com/mkdocstrings/griffe) from 1.6.2 to 1.7.1.
- [Release notes](https://github.com/mkdocstrings/griffe/releases)
- [Changelog](https://github.com/mkdocstrings/griffe/blob/main/CHANGELOG.md)
- [Commits](https://github.com/mkdocstrings/griffe/compare/1.6.2...1.7.1)

---
updated-dependencies:
- dependency-name: griffe
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-31 19:34:08 +00:00
github-actions[bot]
53adc9e18a
Merge pull request #1081 from django-components/dependabot/pip/tox-4.25.0
build(deps): bump tox from 4.24.2 to 4.25.0
2025-03-31 21:33:10 +02:00
dependabot[bot]
184c3e4c6c
build(deps): bump tox from 4.24.2 to 4.25.0
Bumps [tox](https://github.com/tox-dev/tox) from 4.24.2 to 4.25.0.
- [Release notes](https://github.com/tox-dev/tox/releases)
- [Changelog](https://github.com/tox-dev/tox/blob/main/docs/changelog.rst)
- [Commits](https://github.com/tox-dev/tox/compare/4.24.2...4.25.0)

---
updated-dependencies:
- dependency-name: tox
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-31 17:15:33 +00:00
components-release-bot
22dac99e4e Add benchmark results for 0.135 2025-03-31 14:17:45 +00:00
258 changed files with 23881 additions and 7509 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,32 @@
// For format details, see https://aka.ms/devcontainer.json. For config options, see the README at:
// https://github.com/microsoft/vscode-dev-containers/tree/v0.245.0/containers/python-3
{
// Uncomment to run Python 3.13 or other specific version
// "image": "mcr.microsoft.com/devcontainers/python:3.13-bullseye",
// Configure tool-specific properties.
"customizations": {
// Configure properties specific to VS Code.
"vscode": {
// Set *default* container specific settings.json values on container create.
"settings": {
"python.defaultInterpreterPath": "/usr/local/bin/python",
"python.linting.enabled": true
},
// Add the IDs of extensions you want installed when the container is created.
"extensions": [
"ms-python.python",
"ms-python.vscode-pylance",
"ms-python.vscode-python-envs",
"jurooravec.python-inline-source-2"
]
}
}
// Use 'forwardPorts' to make a list of ports inside the container available locally.
// "forwardPorts": [],
// Use 'postCreateCommand' to run commands after the container is created.
//"postCreateCommand": ""
}

View file

@ -30,7 +30,7 @@ jobs:
# Authenticate with git with the Github App that has permission
# to push to master, in order to push benchmark results.
# See https://stackoverflow.com/a/79142962/9788634
- uses: actions/create-github-app-token@v1
- uses: actions/create-github-app-token@v2
id: app-token
with:
app-id: ${{ vars.RELEASE_BOT_APP_ID }}
@ -56,7 +56,9 @@ jobs:
- name: Install dependencies
run: |
python -m pip install --upgrade pip wheel
python -m pip install -q hatch pre-commit asv
# NOTE: pin virtualenv to <20.31 until asv fixes it.
# See https://github.com/airspeed-velocity/asv/issues/1484
python -m pip install -q hatch pre-commit asv virtualenv==20.30
hatch --version
###########################################

View file

@ -39,7 +39,9 @@ jobs:
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install asv
# NOTE: pin virtualenv to <20.31 until asv fixes it.
# See https://github.com/airspeed-velocity/asv/issues/1484
pip install asv virtualenv==20.30
- name: Run benchmarks
run: |

View file

@ -14,7 +14,7 @@ jobs:
strategy:
matrix:
python-version: ['3.8', '3.9', '3.10', '3.11', '3.12', '3.13']
os: [ubuntu-20.04, windows-latest]
os: [ubuntu-latest, windows-latest]
steps:
# Configure git to handle long paths
@ -45,7 +45,7 @@ jobs:
# Verify that docs build
test_docs:
runs-on: ubuntu-20.04
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ['3.13']
@ -70,7 +70,7 @@ jobs:
# Verify that the sample project works
test_sampleproject:
runs-on: ubuntu-20.04
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ['3.13']

8
.gitignore vendored
View file

@ -1,3 +1,6 @@
# Project-specific files
sampleproject/staticfiles/
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
@ -43,6 +46,7 @@ htmlcov/
nosetests.xml
coverage.xml
*,cover
.pytest_cache/
# Translations
*.mo
@ -76,7 +80,11 @@ poetry.lock
site
.direnv/
.envrc
.mypy_cache/
# JS, NPM Dependency directories
node_modules/
jspm_packages/
# Cursor
.cursorrules

File diff suppressed because it is too large Load diff

304
README.md
View file

@ -23,9 +23,10 @@ A component in django-components can be as simple as a Django template and Pytho
```
```py
# components/calendar/calendar.html
from django_components import Component
# components/calendar/calendar.py
from django_components import Component, register
@register("calendar")
class Calendar(Component):
template_file = "calendar.html"
```
@ -56,15 +57,16 @@ document.querySelector(".calendar").onclick = () => {
```py
# components/calendar/calendar.py
from django_components import Component
from django_components import Component, register
@register("calendar")
class Calendar(Component):
template_file = "calendar.html"
js_file = "calendar.js"
css_file = "calendar.css"
def get_context_data(self, date):
return {"date": date}
def get_template_data(self, args, kwargs, slots, context):
return {"date": kwargs["date"]}
```
Use the component like this:
@ -121,21 +123,23 @@ class Calendar(Component):
# Additional JS and CSS
class Media:
js = ["https://cdn.jsdelivr.net/npm/htmx.org@2.1.1/dist/htmx.min.js"]
js = ["https://cdn.jsdelivr.net/npm/htmx.org@2/dist/htmx.min.js"]
css = ["bootstrap/dist/css/bootstrap.min.css"]
# Variables available in the template
def get_context_data(self, date):
def get_template_data(self, args, kwargs, slots, context):
return {
"date": date
"date": kwargs["date"]
}
```
### Composition with slots
- Render components inside templates with `{% component %}` tag.
- Compose them with `{% slot %}` and `{% fill %}` tags.
- Vue-like slot system, including scoped slots.
- Render components inside templates with
[`{% component %}`](https://django-components.github.io/django-components/latest/reference/template_tags#component) tag.
- Compose them with [`{% slot %}`](https://django-components.github.io/django-components/latest/reference/template_tags#slot)
and [`{% fill %}`](https://django-components.github.io/django-components/latest/reference/template_tags#fill) tags.
- Vue-like slot system, including [scoped slots](https://django-components.github.io/django-components/latest/concepts/fundamentals/slots/#slot-data).
```django
{% component "Layout"
@ -169,14 +173,17 @@ class Calendar(Component):
### Extended template tags
`django-components` extends Django's template tags syntax with:
`django-components` is designed for flexibility, making working with templates a breeze.
- Literal lists and dictionaries in template tags
- Self-closing tags `{% mytag / %}`
- Multi-line template tags
- Spread operator `...` to dynamically pass args or kwargs into the template tag
- Nested template tags like `"{{ first_name }} {{ last_name }}"`
- Flat definition of dictionary keys `attr:key=val`
It extends Django's template tags syntax with:
<!-- TODO - Document literal lists and dictionaries -->
- Literal lists and dictionaries in the template
- [Self-closing tags](https://django-components.github.io/django-components/latest/concepts/fundamentals/template_tag_syntax#self-closing-tags) `{% mytag / %}`
- [Multi-line template tags](https://django-components.github.io/django-components/latest/concepts/fundamentals/template_tag_syntax#multiline-tags)
- [Spread operator](https://django-components.github.io/django-components/latest/concepts/fundamentals/template_tag_syntax#spread-operator) `...` to dynamically pass args or kwargs into the template tag
- [Template tags inside literal strings](https://django-components.github.io/django-components/latest/concepts/fundamentals/template_tag_syntax#template-tags-inside-literal-strings) like `"{{ first_name }} {{ last_name }}"`
- [Pass dictonaries by their key-value pairs](https://django-components.github.io/django-components/latest/concepts/fundamentals/template_tag_syntax#pass-dictonary-by-its-key-value-pairs) `attr:key=val`
```django
{% component "table"
@ -201,13 +208,70 @@ class Calendar(Component):
/ %}
```
You too can define template tags with these features by using
[`@template_tag()`](https://django-components.github.io/django-components/latest/reference/api/#django_components.template_tag)
or [`BaseNode`](https://django-components.github.io/django-components/latest/reference/api/#django_components.BaseNode).
Read more on [Custom template tags](https://django-components.github.io/django-components/latest/concepts/advanced/template_tags/).
### Full programmatic access
When you render a component, you can access everything about the component:
- Component input: [args, kwargs, slots and context](https://django-components.github.io/django-components/latest/concepts/fundamentals/render_api/#component-inputs)
- Component's template, CSS and JS
- Django's [context processors](https://django-components.github.io/django-components/latest/concepts/fundamentals/render_api/#request-and-context-processors)
- Unique [render ID](https://django-components.github.io/django-components/latest/concepts/fundamentals/render_api/#component-id)
```python
class Table(Component):
js_file = "table.js"
css_file = "table.css"
template = """
<div class="table">
<span>{{ variable }}</span>
</div>
"""
def get_template_data(self, args, kwargs, slots, context):
# Access component's ID
assert self.id == "djc1A2b3c"
# Access component's inputs and slots
assert self.args == [123, "str"]
assert self.kwargs == {"variable": "test", "another": 1}
footer_slot = self.slots["footer"]
some_var = self.context["some_var"]
# Access the request object and Django's context processors, if available
assert self.request.GET == {"query": "something"}
assert self.context_processors_data['user'].username == "admin"
return {
"variable": kwargs["variable"],
}
# Access component's HTML / JS / CSS
Table.template
Table.js
Table.css
# Render the component
rendered = Table.render(
kwargs={"variable": "test", "another": 1},
args=(123, "str"),
slots={"footer": "MY_FOOTER"},
)
```
### Granular HTML attributes
Use the [`{% html_attrs %}`](https://django-components.github.io/django-components/latest/concepts/fundamentals/html_attributes/) template tag to render HTML attributes.
It supports:
- Defining attributes as dictionaries
- Defining attributes as keyword arguments
- Defining attributes as whole dictionaries or keyword arguments
- Merging attributes from multiple sources
- Boolean attributes
- Appending attributes
@ -224,13 +288,19 @@ It supports:
>
```
`{% html_attrs %}` offers a Vue-like granular control over `class` and `style` HTML attributes,
[`{% html_attrs %}`](https://django-components.github.io/django-components/latest/concepts/fundamentals/html_attributes/) offers a Vue-like granular control for
[`class`](https://django-components.github.io/django-components/latest/concepts/fundamentals/html_attributes/#merging-class-attributes)
and [`style`](https://django-components.github.io/django-components/latest/concepts/fundamentals/html_attributes/#merging-style-attributes)
HTML attributes,
where you can use a dictionary to manage each class name or style property separately.
```django
{% html_attrs
class="foo bar"
class={"baz": True, "foo": False}
class={
"baz": True,
"foo": False,
}
class="extra"
%}
```
@ -238,7 +308,11 @@ where you can use a dictionary to manage each class name or style property separ
```django
{% html_attrs
style="text-align: center; background-color: blue;"
style={"background-color": "green", "color": None, "width": False}
style={
"background-color": "green",
"color": None,
"width": False,
}
style="position: absolute; height: 12px;"
%}
```
@ -247,11 +321,13 @@ Read more about [HTML attributes](https://django-components.github.io/django-com
### HTML fragment support
`django-components` makes integration with HTMX, AlpineJS or jQuery easy by allowing components to be rendered as HTML fragments:
`django-components` makes integration with HTMX, AlpineJS or jQuery easy by allowing components to be rendered as [HTML fragments](https://django-components.github.io/django-components/latest/concepts/advanced/html_fragments/):
- Components's JS and CSS is loaded automatically when the fragment is inserted into the DOM.
- Components's JS and CSS files are loaded automatically when the fragment is inserted into the DOM.
- Expose components as views with `get`, `post`, `put`, `patch`, `delete` methods
- Components can be [exposed as Django Views](https://django-components.github.io/django-components/latest/concepts/fundamentals/component_views_urls/) with `get()`, `post()`, `put()`, `patch()`, `delete()` methods
- Automatically create an endpoint for a component with [`Component.View.public`](https://django-components.github.io/django-components/latest/concepts/fundamentals/component_views_urls/#register-urls-automatically)
```py
# components/calendar/calendar.py
@ -259,94 +335,163 @@ Read more about [HTML attributes](https://django-components.github.io/django-com
class Calendar(Component):
template_file = "calendar.html"
def get(self, request, *args, **kwargs):
page = request.GET.get("page", 1)
return self.render_to_response(
kwargs={
"page": page,
}
)
class View:
# Register Component with `urlpatterns`
public = True
def get_context_data(self, page):
# Define handlers
def get(self, request, *args, **kwargs):
page = request.GET.get("page", 1)
return self.component.render_to_response(
request=request,
kwargs={
"page": page,
},
)
def get_template_data(self, args, kwargs, slots, context):
return {
"page": page,
"page": kwargs["page"],
}
# urls.py
path("calendar/", Calendar.as_view()),
# Get auto-generated URL for the component
url = get_component_url(Calendar)
# Or define explicit URL in urls.py
path("calendar/", Calendar.as_view())
```
### Type hints
### Provide / Inject
Opt-in to type hints by defining types for component's args, kwargs, slots, and more:
`django-components` supports the provide / inject pattern, similarly to React's [Context Providers](https://react.dev/learn/passing-data-deeply-with-context) or Vue's [provide / inject](https://vuejs.org/guide/components/provide-inject):
- Use the [`{% provide %}`](https://django-components.github.io/django-components/latest/reference/template_tags/#provide) tag to provide data to the component tree
- Use the [`Component.inject()`](https://django-components.github.io/django-components/latest/reference/api/#django_components.Component.inject) method to inject data into the component
Read more about [Provide / Inject](https://django-components.github.io/django-components/latest/concepts/advanced/provide_inject).
```django
<body>
{% provide "theme" variant="light" %}
{% component "header" / %}
{% endprovide %}
</body>
```
```djc_py
@register("header")
class Header(Component):
template = "..."
def get_template_data(self, args, kwargs, slots, context):
theme = self.inject("theme").variant
return {
"theme": theme,
}
```
### Input validation and static type hints
Avoid needless errors with [type hints and runtime input validation](https://django-components.github.io/django-components/latest/concepts/fundamentals/typing_and_validation/).
To opt-in to input validation, define types for component's args, kwargs, slots, and more:
```py
from typing import NotRequired, Tuple, TypedDict, SlotContent, SlotFunc
from typing import NamedTuple, Optional
from django.template import Context
from django_components import Component, Slot, SlotInput
ButtonArgs = Tuple[int, str]
class Button(Component):
class Args(NamedTuple):
size: int
text: str
class ButtonKwargs(TypedDict):
variable: str
another: int
maybe_var: NotRequired[int] # May be omitted
class Kwargs(NamedTuple):
variable: str
another: int
maybe_var: Optional[int] = None # May be omitted
class ButtonData(TypedDict):
variable: str
class Slots(NamedTuple):
my_slot: Optional[SlotInput] = None
another_slot: SlotInput
class ButtonSlots(TypedDict):
my_slot: NotRequired[SlotFunc]
another_slot: SlotContent
ButtonType = Component[ButtonArgs, ButtonKwargs, ButtonSlots, ButtonData, JsData, CssData]
class Button(ButtonType):
def get_context_data(self, *args, **kwargs):
self.input.args[0] # int
self.input.kwargs["variable"] # str
self.input.slots["my_slot"] # SlotFunc[MySlotData]
return {} # Error: Key "variable" is missing
def get_template_data(self, args: Args, kwargs: Kwargs, slots: Slots, context: Context):
args.size # int
kwargs.variable # str
slots.my_slot # Slot[MySlotData]
```
When you then call `Button.render()` or `Button.render_to_response()`, you will get type hints:
To have type hints when calling
[`Button.render()`](https://django-components.github.io/django-components/latest/reference/api/#django_components.Component.render) or
[`Button.render_to_response()`](https://django-components.github.io/django-components/latest/reference/api/#django_components.Component.render_to_response),
wrap the inputs in their respective `Args`, `Kwargs`, and `Slots` classes:
```py
Button.render(
# Error: First arg must be `int`, got `float`
args=(1.25, "abc"),
args=Button.Args(
size=1.25,
text="abc",
),
# Error: Key "another" is missing
kwargs={
"variable": "text",
},
kwargs=Button.Kwargs(
variable="text",
),
)
```
### Extensions
Django-components functionality can be extended with "extensions". Extensions allow for powerful customization and integrations. They can:
Django-components functionality can be extended with [Extensions](https://django-components.github.io/django-components/latest/concepts/advanced/extensions/).
Extensions allow for powerful customization and integrations. They can:
- Tap into lifecycle events, such as when a component is created, deleted, or registered.
- Add new attributes and methods to the components under an extension-specific nested class.
- Tap into lifecycle events, such as when a component is created, deleted, or registered
- Add new attributes and methods to the components
- Add custom CLI commands
- Add custom URLs
Some of the extensions include:
- [Component caching](https://github.com/django-components/django-components/blob/master/src/django_components/extensions/cache.py)
- [Django View integration](https://github.com/django-components/django-components/blob/master/src/django_components/extensions/view.py)
- [Component defaults](https://github.com/django-components/django-components/blob/master/src/django_components/extensions/defaults.py)
- [Pydantic integration (input validation)](https://github.com/django-components/djc-ext-pydantic)
Some of the planned extensions include:
- Caching
- AlpineJS integration
- Storybook integration
- Pydantic validation
- Component-level benchmarking with asv
### Caching
- [Components can be cached](https://django-components.github.io/django-components/latest/concepts/advanced/component_caching/) using Django's cache framework.
- Caching rules can be configured on a per-component basis.
- Components are cached based on their input. Or you can write custom caching logic.
```py
from django_components import Component
class MyComponent(Component):
class Cache:
enabled = True
ttl = 60 * 60 * 24 # 1 day
def hash(self, *args, **kwargs):
return hash(f"{json.dumps(args)}:{json.dumps(kwargs)}")
```
### Simple testing
- Write tests for components with `@djc_test` decorator.
- Write tests for components with [`@djc_test`](https://django-components.github.io/django-components/latest/concepts/advanced/testing/) decorator.
- The decorator manages global state, ensuring that tests don't leak.
- If using `pytest`, the decorator allows you to parametrize Django or Components settings.
- The decorator also serves as a stand-in for Django's `@override_settings`.
- The decorator also serves as a stand-in for Django's [`@override_settings`](https://docs.djangoproject.com/en/5.2/topics/testing/tools/#django.test.override_settings).
```python
from djc_test import djc_test
from django_components.testing import djc_test
from components.my_component import MyTable
from components.my_table import MyTable
@djc_test
def test_my_table():
@ -358,11 +503,6 @@ def test_my_table():
assert rendered == "<table>My table</table>"
```
### Handle large projects with ease
- Components can be infinitely nested.
- (Soon) Optimize performance with component-level caching
### Debugging features
- **Visual component inspection**: Highlight components and slots directly in your browser.
@ -386,10 +526,6 @@ def test_my_table():
{% endcalendar %}
```
### Other features
- Vue-like provide / inject system
## Documentation
[Read the full documentation here](https://django-components.github.io/django-components/latest/).
@ -418,7 +554,7 @@ to see the latest features and fixes.
One of our goals with `django-components` is to make it easy to share components between projects. If you have a set of components that you think would be useful to others, please open a pull request to add them to the list below.
- [django-htmx-components](https://github.com/iwanalabs/django-htmx-components): A set of components for use with [htmx](https://htmx.org/). Try out the [live demo](https://dhc.iwanalabs.com/).
- [django-htmx-components](https://github.com/iwanalabs/django-htmx-components): A set of components for use with [htmx](https://htmx.org/).
- [djc-heroicons](https://pypi.org/project/djc-heroicons/): A component that renders icons from [Heroicons.com](https://heroicons.com/).

View file

@ -26,7 +26,7 @@ django-components uses `asv` for these use cases:
1. When a git tag is created and pushed, we also update the documentation website (see `docs.yml`).
2. Before we publish the docs website, we generate the HTML report for the benchmark results.
3. The generated report is placed in the `docs/benchmarks/` directory, and is thus
published with the rest of the docs website and available under [`/benchmarks/`](https://django-components.github.io/django-components/benchmarks).
published with the rest of the docs website and available under [`/benchmarks/`](https://django-components.github.io/django-components/latest/benchmarks).
- NOTE: The location where the report is placed is defined in `asv.conf.json`.
- Compare performance between commits on pull requests:

View file

@ -1 +1 @@
[[1662, [52920320.0, 54566912.0]], [1672, [52350976.0, 54599680.0]], [1687, [52109312.0, 54779904.0]], [1691, [52899840.0, 54730752.0]]]
[[1662, [52920320.0, 54566912.0]], [1672, [52350976.0, 54599680.0]], [1687, [52109312.0, 54779904.0]], [1691, [52899840.0, 54730752.0]], [1709, [52936704.0, 55009280.0]], [1726, [52379648.0, 54992896.0]], [1766, [53084160.0, 55382016.0]], [1770, [53047296.0, 55373824.0]], [1776, [52490240.0, 55361536.0]], [1801, [53153792.0, 55410688.0]], [1937, [52957184.0, 55177216.0]], [1960, [52932608.0, 55693312.0]], [1996, [53096448.0, 55484416.0]], [2029, [52715520.0, 56090624.0]]]

View file

@ -1 +1 @@
[[1662, [53800960.0, 54734848.0]], [1672, [52289536.0, 55099392.0]], [1687, [52142080.0, 55255040.0]], [1691, [53796864.0, 55238656.0]]]
[[1662, [53800960.0, 54734848.0]], [1672, [52289536.0, 55099392.0]], [1687, [52142080.0, 55255040.0]], [1691, [53796864.0, 55238656.0]], [1709, [53768192.0, 55455744.0]], [1726, [51998720.0, 55451648.0]], [1766, [53739520.0, 55812096.0]], [1770, [53948416.0, 55824384.0]], [1776, [52097024.0, 55791616.0]], [1801, [53919744.0, 55799808.0]], [1937, [52822016.0, 56242176.0]], [1960, [53063680.0, 56180736.0]], [1996, [53018624.0, 56389632.0]], [2029, [52736000.0, 56791040.0]]]

View file

@ -1 +1 @@
[[1662, [44191744.0, 44191744.0]], [1672, [44056576.0, 44048384.0]], [1687, [44191744.0, 44310528.0]], [1691, [44183552.0, 44175360.0]]]
[[1662, [44191744.0, 44191744.0]], [1672, [44056576.0, 44048384.0]], [1687, [44191744.0, 44310528.0]], [1691, [44183552.0, 44175360.0]], [1709, [44191744.0, 44314624.0]], [1726, [44195840.0, 44314624.0]], [1766, [44322816.0, 44314624.0]], [1770, [44326912.0, 44322816.0]], [1776, [44183552.0, 44306432.0]], [1801, [44195840.0, 44453888.0]], [1937, [44756992.0, 44744704.0]], [1960, [44716032.0, 44834816.0]], [1996, [44716032.0, 44969984.0]], [2029, [44871680.0, 44912640.0]]]

View file

@ -1 +1 @@
[[1662, [44195840.0, 44187648.0]], [1672, [44060672.0, 43917312.0]], [1687, [44105728.0, 44310528.0]], [1691, [44187648.0, 44183552.0]]]
[[1662, [44195840.0, 44187648.0]], [1672, [44060672.0, 43917312.0]], [1687, [44105728.0, 44310528.0]], [1691, [44187648.0, 44183552.0]], [1709, [44191744.0, 44437504.0]], [1726, [44322816.0, 44314624.0]], [1766, [44322816.0, 44310528.0]], [1770, [44101632.0, 44310528.0]], [1776, [44314624.0, 44437504.0]], [1801, [44191744.0, 44453888.0]], [1937, [44527616.0, 44744704.0]], [1960, [44716032.0, 44838912.0]], [1996, [44724224.0, 44969984.0]], [2029, [44617728.0, 44986368.0]]]

View file

@ -1 +1 @@
[[1662, [0.06960565700001098, 0.25608221199996706]], [1672, [0.07114163800000028, 0.26389872900000455]], [1687, [0.06910802600003763, 0.25746033199999374]], [1691, [0.07048037500001669, 0.2598985070000026]]]
[[1662, [0.06960565700001098, 0.25608221199996706]], [1672, [0.07114163800000028, 0.26389872900000455]], [1687, [0.06910802600003763, 0.25746033199999374]], [1691, [0.07048037500001669, 0.2598985070000026]], [1709, [0.07402671400001282, 0.26584690599997884]], [1726, [0.07297276199997782, 0.2569234329999972]], [1766, [0.07308550800001967, 0.26274096600002395]], [1770, [0.0749189080000292, 0.26436952000000247]], [1776, [0.07303507899999317, 0.2628890319999755]], [1801, [0.07360306399999672, 0.2678246009999725]], [1937, [0.07941284200001064, 0.26779402600004687]], [1960, [0.08026317200000221, 0.26819844099998136]], [1996, [0.0814841690000776, 0.28364495499999975]], [2029, [0.08105427499998541, 0.29477426600001877]]]

View file

@ -1 +1 @@
[[1662, [0.03327357099999517, 0.1421111020000012]], [1672, [0.033918617999972867, 0.14395761299999776]], [1687, [0.03317536700001256, 0.14245594600001255]], [1691, [0.034316510999985894, 0.1444248799999741]]]
[[1662, [0.03327357099999517, 0.1421111020000012]], [1672, [0.033918617999972867, 0.14395761299999776]], [1687, [0.03317536700001256, 0.14245594600001255]], [1691, [0.034316510999985894, 0.1444248799999741]], [1709, [0.03742426899998463, 0.14901454800002512]], [1726, [0.03658580800001232, 0.1459621130000528]], [1766, [0.03723830100000214, 0.15196534300002895]], [1770, [0.03752758399997447, 0.15356457899997622]], [1776, [0.03678920999999491, 0.14955294699998944]], [1801, [0.037022983000014165, 0.15138703899998518]], [1937, [0.043317416999911984, 0.15457556900003055]], [1960, [0.04349111400000538, 0.15453611999998884]], [1996, [0.04362213900003553, 0.16551773399999092]], [2029, [0.043648402000002307, 0.17461173199995983]]]

View file

@ -1 +1 @@
[[1662, [0.0035443229999998493, 0.00467639600003622]], [1672, [0.0036137869999777195, 0.004807943000002979]], [1687, [0.0035223549999727766, 0.004706463999980315]], [1691, [0.00364059099999281, 0.004926952999994683]]]
[[1662, [0.0035443229999998493, 0.00467639600003622]], [1672, [0.0036137869999777195, 0.004807943000002979]], [1687, [0.0035223549999727766, 0.004706463999980315]], [1691, [0.00364059099999281, 0.004926952999994683]], [1709, [0.003602947999979733, 0.004853936999950292]], [1726, [0.0035008030000085455, 0.004695608999981005]], [1766, [0.003566315000000486, 0.004791812000007667]], [1770, [0.0036766670000361046, 0.004929383999979109]], [1776, [0.0035613420000117912, 0.004760385999986738]], [1801, [0.003639607999986083, 0.004848561000017071]], [1937, [0.0036632869999948525, 0.00493345400002454]], [1960, [0.0036145729999930154, 0.004811176000004025]], [1996, [0.00375721499995052, 0.0049729269999261305]], [2029, [0.0037106409999978496, 0.004899473999955717]]]

View file

@ -1 +1 @@
[[1662, [0.00010400499999718704, 0.0005328339999977061]], [1672, [0.00010086800000408402, 0.0005549249999887707]], [1687, [9.818199998790078e-05, 0.0005511469999817109]], [1691, [0.0001005780000014056, 0.0005555879999974422]]]
[[1662, [0.00010400499999718704, 0.0005328339999977061]], [1672, [0.00010086800000408402, 0.0005549249999887707]], [1687, [9.818199998790078e-05, 0.0005511469999817109]], [1691, [0.0001005780000014056, 0.0005555879999974422]], [1709, [0.00012266099997759738, 0.0005711430000019391]], [1726, [0.00011641800000461444, 0.0005489540000098714]], [1766, [0.00011609900002440554, 0.0005779780000239043]], [1770, [0.0001176700000087294, 0.0005864990000077341]], [1776, [0.00011622699999236374, 0.0005842630000074678]], [1801, [0.00011665800002447213, 0.000582710000003317]], [1937, [0.00012153600005149201, 0.0005999570000199128]], [1960, [0.00012332000000014887, 0.0005915369999911491]], [1996, [0.00012686900004155177, 0.0006182140000419167]], [2029, [0.00012706900002967814, 0.0006100459999629493]]]

View file

@ -1 +1 @@
[[1662, [0.21775109000003567, 0.21398552899995593]], [1672, [0.22476057199997967, 0.22048105400000395]], [1687, [0.21809406599999193, 0.2131839880000257]], [1691, [0.22356123500000535, 0.22167734499998915]]]
[[1662, [0.21775109000003567, 0.21398552899995593]], [1672, [0.22476057199997967, 0.22048105400000395]], [1687, [0.21809406599999193, 0.2131839880000257]], [1691, [0.22356123500000535, 0.22167734499998915]], [1709, [0.22133603999998286, 0.21805855799999563]], [1726, [0.2166100470000174, 0.21420494400001644]], [1766, [0.22339861599999722, 0.22020213500002228]], [1770, [0.22985272800002576, 0.22544496099999378]], [1776, [0.22073260000001937, 0.2182690520000392]], [1801, [0.224061646999985, 0.2246476189999953]], [1937, [0.22743783699991127, 0.226070988999993]], [1960, [0.2252378419999843, 0.2247263650000093]], [1996, [0.23076480500003527, 0.23163660399995933]], [2029, [0.22799248500001568, 0.22723498599998493]]]

View file

@ -1 +1 @@
[[1662, 0.19832900800003017], [1672, 0.20217585500000723], [1687, 0.19726691500000015], [1691, 0.20350580199999513]]
[[1662, 0.19832900800003017], [1672, 0.20217585500000723], [1687, 0.19726691500000015], [1691, 0.20350580199999513], [1709, 0.19950735400001918], [1726, 0.19625152499997967], [1766, 0.20073733000003813], [1770, 0.20376683500001036], [1776, 0.19919827600000417], [1801, 0.2053688209999791], [1937, 0.2063091950000171], [1960, 0.20468290799999522], [1996, 0.21042045099989082], [2029, 0.2056691309999792]]

View file

@ -1 +1 @@
[[1662, [54439936.0, 53968896.0]], [1672, [54616064.0, 54140928.0]], [1687, [54767616.0, 54296576.0]], [1691, [54743040.0, 54087680.0]]]
[[1662, [54439936.0, 53968896.0]], [1672, [54616064.0, 54140928.0]], [1687, [54767616.0, 54296576.0]], [1691, [54743040.0, 54087680.0]], [1709, [55001088.0, 54312960.0]], [1726, [54992896.0, 54345728.0]], [1766, [55373824.0, 54894592.0]], [1770, [55246848.0, 54898688.0]], [1776, [55357440.0, 54874112.0]], [1801, [55382016.0, 54882304.0]], [1937, [55222272.0, 54722560.0]], [1960, [55263232.0, 54693888.0]], [1996, [55476224.0, 54968320.0]], [2029, [56090624.0, 55582720.0]]]

View file

@ -1 +1 @@
[[1662, [54968320.0, 54792192.0]], [1672, [54849536.0, 54841344.0]], [1687, [55271424.0, 55304192.0]], [1691, [54984704.0, 54964224.0]]]
[[1662, [54968320.0, 54792192.0]], [1672, [54849536.0, 54841344.0]], [1687, [55271424.0, 55304192.0]], [1691, [54984704.0, 54964224.0]], [1709, [55439360.0, 55369728.0]], [1726, [55455744.0, 55177216.0]], [1766, [55545856.0, 55631872.0]], [1770, [55812096.0, 55611392.0]], [1776, [55640064.0, 55631872.0]], [1801, [55812096.0, 55902208.0]], [1937, [56008704.0, 56143872.0]], [1960, [55783424.0, 56160256.0]], [1996, [56352768.0, 56516608.0]], [2029, [56786944.0, 56778752.0]]]

View file

@ -1 +1 @@
[[1662, [44187648.0, 44183552.0]], [1672, [44048384.0, 44048384.0]], [1687, [44314624.0, 44310528.0]], [1691, [44179456.0, 44175360.0]]]
[[1662, [44187648.0, 44183552.0]], [1672, [44048384.0, 44048384.0]], [1687, [44314624.0, 44310528.0]], [1691, [44179456.0, 44175360.0]], [1709, [44314624.0, 44310528.0]], [1726, [44314624.0, 44314624.0]], [1766, [44318720.0, 44314624.0]], [1770, [44322816.0, 44314624.0]], [1776, [44306432.0, 44240896.0]], [1801, [44453888.0, 44453888.0]], [1937, [44744704.0, 44744704.0]], [1960, [44838912.0, 44838912.0]], [1996, [44969984.0, 44969984.0]], [2029, [44843008.0, 44851200.0]]]

View file

@ -1 +1 @@
[[1662, [44187648.0, 44187648.0]], [1672, [44052480.0, 44052480.0]], [1687, [44314624.0, 44310528.0]], [1691, [44179456.0, 44179456.0]]]
[[1662, [44187648.0, 44187648.0]], [1672, [44052480.0, 44052480.0]], [1687, [44314624.0, 44310528.0]], [1691, [44179456.0, 44179456.0]], [1709, [44310528.0, 44314624.0]], [1726, [44314624.0, 44314624.0]], [1766, [44310528.0, 44314624.0]], [1770, [44314624.0, 44318720.0]], [1776, [44437504.0, 44437504.0]], [1801, [44449792.0, 44449792.0]], [1937, [44744704.0, 44744704.0]], [1960, [44965888.0, 44834816.0]], [1996, [44974080.0, 44974080.0]], [2029, [44982272.0, 44986368.0]]]

View file

@ -1 +1 @@
[[1662, [0.2574955810000006, 0.2591010970000127]], [1672, [0.2600247560000071, 0.26185358800000813]], [1687, [0.2567828300000201, 0.2602957870000182]], [1691, [0.259077934000004, 0.2619792840000059]]]
[[1662, [0.2574955810000006, 0.2591010970000127]], [1672, [0.2600247560000071, 0.26185358800000813]], [1687, [0.2567828300000201, 0.2602957870000182]], [1691, [0.259077934000004, 0.2619792840000059]], [1709, [0.2646600410000133, 0.2676605120000204]], [1726, [0.2570519909999689, 0.2606809000000112]], [1766, [0.262679922000018, 0.2686107789999994]], [1770, [0.265977821000007, 0.26914772099999595]], [1776, [0.2626667089999728, 0.2663110299999971]], [1801, [0.2658582709999848, 0.2712929850000023]], [1937, [0.2675778039999841, 0.2724974679999832]], [1960, [0.26819597400000816, 0.2740507329999957]], [1996, [0.2794132599999557, 0.28440619299999526]], [2029, [0.2920349000000044, 0.2976166970000236]]]

View file

@ -1 +1 @@
[[1662, [0.14273938200000202, 0.1464969190000147]], [1672, [0.14515931700000806, 0.14909453600000688]], [1687, [0.1423055980000072, 0.14642362500001127]], [1691, [0.1436571560000175, 0.14915657599999577]]]
[[1662, [0.14273938200000202, 0.1464969190000147]], [1672, [0.14515931700000806, 0.14909453600000688]], [1687, [0.1423055980000072, 0.14642362500001127]], [1691, [0.1436571560000175, 0.14915657599999577]], [1709, [0.14860135300000366, 0.15305296299999327]], [1726, [0.14520097999997006, 0.14991973799999414]], [1766, [0.15071133700001837, 0.15540660900001058]], [1770, [0.15150350199996865, 0.1558047899999906]], [1776, [0.14876902899999322, 0.15549233400000162]], [1801, [0.15248822700002052, 0.15465820200000735]], [1937, [0.15459265900005903, 0.15926110200007315]], [1960, [0.15396625699997912, 0.16023626799997714]], [1996, [0.16650312799993117, 0.17177308600003016]], [2029, [0.17414895399997476, 0.178393189000019]]]

View file

@ -1 +1 @@
[[1662, [0.004720848000005162, 0.004705489000002672]], [1672, [0.004856270999994194, 0.00490694800001279]], [1687, [0.00473016699999107, 0.004734037999980956]], [1691, [0.004871503999993365, 0.0048899079999955575]]]
[[1662, [0.004720848000005162, 0.004705489000002672]], [1672, [0.004856270999994194, 0.00490694800001279]], [1687, [0.00473016699999107, 0.004734037999980956]], [1691, [0.004871503999993365, 0.0048899079999955575]], [1709, [0.0048215560000244295, 0.004858458999990489]], [1726, [0.004671787999996013, 0.004672599999992144]], [1766, [0.00478528000002143, 0.0047485900000197034]], [1770, [0.004901490999998259, 0.004895917999988342]], [1776, [0.00480728600001612, 0.00472804499997892]], [1801, [0.004847185000016907, 0.004857667999999649]], [1937, [0.004923484000073586, 0.004925836999973399]], [1960, [0.004825538000005736, 0.0047952310000027865]], [1996, [0.005049280000093859, 0.004947880000145233]], [2029, [0.004897051999989799, 0.004863266000029398]]]

View file

@ -1 +1 @@
[[1662, [0.0005377129999999397, 0.0005395769999836375]], [1672, [0.000547750000009728, 0.0005677989999810507]], [1687, [0.0005471899999918151, 0.0005447550000212686]], [1691, [0.0005559489999882317, 0.0005480739999939033]]]
[[1662, [0.0005377129999999397, 0.0005395769999836375]], [1672, [0.000547750000009728, 0.0005677989999810507]], [1687, [0.0005471899999918151, 0.0005447550000212686]], [1691, [0.0005559489999882317, 0.0005480739999939033]], [1709, [0.0005736080000247057, 0.0005720849999875099]], [1726, [0.000542692999999872, 0.0005430530000012368]], [1766, [0.0005853119999983392, 0.000582014999963576]], [1770, [0.0005929909999622396, 0.000583071999983531]], [1776, [0.0005810670000130358, 0.000576186999978745]], [1801, [0.0005717709999828458, 0.0005785939999896073]], [1937, [0.0005969709999362749, 0.0005864510000037626]], [1960, [0.0005953940000154034, 0.0005933700000468889]], [1996, [0.0006160310000495883, 0.0006166809999967882]], [2029, [0.0006159270000125616, 0.0006080119999865019]]]

View file

@ -1 +1 @@
[[1662, [0.21402431699999624, 0.21364062999998623]], [1672, [0.2221746719999942, 0.2222580240000127]], [1687, [0.2142312400000037, 0.21397752699999728]], [1691, [0.22129613300000983, 0.21942976399998315]]]
[[1662, [0.21402431699999624, 0.21364062999998623]], [1672, [0.2221746719999942, 0.2222580240000127]], [1687, [0.2142312400000037, 0.21397752699999728]], [1691, [0.22129613300000983, 0.21942976399998315]], [1709, [0.2199001029999863, 0.22046102699999892]], [1726, [0.2147675530000015, 0.21506381099999317]], [1766, [0.22056839900000114, 0.21916191200000412]], [1770, [0.22394285699999728, 0.22330144500000415]], [1776, [0.21867883100003382, 0.21859779499999377]], [1801, [0.22378945699995256, 0.22211803700002974]], [1937, [0.22545313400001987, 0.22602228000005198]], [1960, [0.22564571399999522, 0.22598634599995648]], [1996, [0.2295973340000046, 0.23030742100002044]], [2029, [0.22777395400004252, 0.2292747939999913]]]

View file

@ -1 +1 @@
[[1662, 53737309.613078326], [1672, 53463506.59363525], [1687, 53427924.42970294], [1691, 53807508.99158667]]
[[1662, 53737309.613078326], [1672, 53463506.59363525], [1687, 53427924.42970294], [1691, 53807508.99158667], [1709, 53963042.655257314], [1726, 53670369.245800875], [1766, 54220916.6140389], [1770, 54198077.75539557], [1776, 53906774.26269022], [1801, 54270509.344660625], [1937, 54055804.31664803], [1960, 54295416.494559616], [1996, 54277301.04707094], [2029, 54376892.25474807]]

View file

@ -1 +1 @@
[[1662, 54265895.0709751], [1672, 53676080.7209516], [1687, 53675997.57883592], [1691, 54512993.537089705]]
[[1662, 54265895.0709751], [1672, 53676080.7209516], [1687, 53675997.57883592], [1691, 54512993.537089705], [1709, 54605449.27839023], [1726, 53697436.790693834], [1766, 54766004.5031032], [1770, 54878384.55144014], [1776, 53912680.86221259], [1801, 54851721.60114168], [1937, 54505276.07990639], [1960, 54599968.83944605], [1996, 54678155.56971878], [2029, 54725974.50425164]]

View file

@ -1 +1 @@
[[1662, 44191743.99999999], [1672, 44052479.80957694], [1687, 44251096.14326895], [1691, 44179455.81012423]]
[[1662, 44191743.99999999], [1672, 44052479.80957694], [1687, 44251096.14326895], [1691, 44179455.81012423], [1709, 44253141.3491094], [1726, 44255192.14695785], [1766, 44318719.81072088], [1770, 44324863.95268679], [1776, 44244949.34121254], [1801, 44324676.21343578], [1937, 44750847.578234404], [1960, 44775384.609963], [1996, 44842828.229087956], [2029, 44892155.328466915]]

View file

@ -1 +1 @@
[[1662, 44191743.81017703], [1672, 43988933.59873213], [1687, 44208009.40445502], [1691, 44185599.95253766]]
[[1662, 44191743.81017703], [1672, 43988933.59873213], [1687, 44208009.40445502], [1691, 44185599.95253766], [1709, 44314453.63272547], [1726, 44318719.81072088], [1766, 44316671.57410231], [1770, 44205956.60747199], [1776, 44376021.4672124], [1801, 44322622.19567646], [1937, 44636028.0238471], [1960, 44777429.84849827], [1996, 44846935.655543014], [2029, 44801668.84315699]]

View file

@ -1 +1 @@
[[1662, 0.13350944016163727], [1672, 0.1370189324406613], [1687, 0.13338881256624893], [1691, 0.13534306127506]]
[[1662, 0.13350944016163727], [1672, 0.1370189324406613], [1687, 0.13338881256624893], [1691, 0.13534306127506], [1709, 0.14028461383291016], [1726, 0.1369248426273554], [1766, 0.13857329097819557], [1770, 0.14073477092350728], [1776, 0.1385645020210802], [1801, 0.14040196312080028], [1937, 0.14582964264952603], [1960, 0.14671897491501892], [1996, 0.15202819951982394], [2029, 0.15457268328939747]]

View file

@ -1 +1 @@
[[1662, 0.0687644082522681], [1672, 0.06987734456556612], [1687, 0.06874611472573841], [1691, 0.07039998567606925]]
[[1662, 0.0687644082522681], [1672, 0.06987734456556612], [1687, 0.06874611472573841], [1691, 0.07039998567606925], [1709, 0.07467771106069107], [1726, 0.07307627413528986], [1766, 0.0752258677863117], [1770, 0.07591381717343901], [1776, 0.0741750279629251], [1801, 0.07486521068773488], [1937, 0.08182795598310513], [1960, 0.08198138820511656], [1996, 0.08497198126158123], [2029, 0.08730133488241124]]

View file

@ -1 +1 @@
[[1662, 0.004071198582731586], [1672, 0.004168318834979474], [1687, 0.004071589002161507], [1691, 0.004235212007582172]]
[[1662, 0.004071198582731586], [1672, 0.004168318834979474], [1687, 0.004071589002161507], [1691, 0.004235212007582172], [1709, 0.004181923314217816], [1726, 0.004054429932062044], [1766, 0.004133897799028137], [1770, 0.004257194320585938], [1776, 0.004117446125697445], [1801, 0.004200816754404154], [1937, 0.004251194879485355], [1960, 0.0041701734817425696], [1996, 0.004322540447211732], [2029, 0.004263823296369016]]

View file

@ -1 +1 @@
[[1662, 0.00023540900613243872], [1672, 0.0002365886195511814], [1687, 0.0002326213978668684], [1691, 0.0002363893607261623]]
[[1662, 0.00023540900613243872], [1672, 0.0002365886195511814], [1687, 0.0002326213978668684], [1691, 0.0002363893607261623], [1709, 0.0002646827752432008], [1726, 0.00025280056719810247], [1766, 0.00025904182642747317], [1770, 0.0002627038966898471], [1776, 0.00026058997620285855], [1801, 0.000260725493948419], [1937, 0.0002700303204925571], [1960, 0.00027008950893915996], [1996, 0.0002800574798090668], [2029, 0.000278420428825539]]

View file

@ -1 +1 @@
[[1662, 0.21586009863792485], [1672, 0.22261052942796597], [1687, 0.21562505130206716], [1691, 0.2226172972159168]]
[[1662, 0.21586009863792485], [1672, 0.22261052942796597], [1687, 0.21562505130206716], [1691, 0.2226172972159168], [1709, 0.21969118716012626], [1726, 0.21540413874268913], [1766, 0.2217946171557135], [1770, 0.22763817627917332], [1776, 0.21949736979633283], [1801, 0.22435444169386096], [1937, 0.22675338309844276], [1960, 0.22498195815021013], [1996, 0.23120029358312028], [2029, 0.22761342037999505]]

View file

@ -1 +1 @@
[[1662, 0.19832900800003017], [1672, 0.20217585500000723], [1687, 0.19726691500000015], [1691, 0.20350580199999513]]
[[1662, 0.19832900800003017], [1672, 0.20217585500000723], [1687, 0.19726691500000015], [1691, 0.20350580199999513], [1709, 0.19950735400001918], [1726, 0.19625152499997967], [1766, 0.20073733000003813], [1770, 0.20376683500001036], [1776, 0.19919827600000417], [1801, 0.2053688209999791], [1937, 0.2063091950000171], [1960, 0.20468290799999522], [1996, 0.21042045099989082], [2029, 0.2056691309999792]]

View file

@ -1 +1 @@
[[1662, 54203904.32644733], [1672, 54377977.05567385], [1687, 54531587.401090905], [1691, 54414373.37457081]]
[[1662, 54203904.32644733], [1672, 54377977.05567385], [1687, 54531587.401090905], [1691, 54414373.37457081], [1709, 54655941.05401974], [1726, 54668354.35558938], [1766, 55133687.30603648], [1770, 55072492.873806104], [1776, 55115246.19008138], [1801, 55131593.83007953], [1937, 54971848.18483294], [1960, 54977822.99733244], [1996, 55221688.06930552], [2029, 55836094.494666085]]

View file

@ -1 +1 @@
[[1662, 54880185.34368702], [1672, 54845439.84705003], [1687, 55287805.57238104], [1691, 54974463.04630629]]
[[1662, 54880185.34368702], [1672, 54845439.84705003], [1687, 55287805.57238104], [1691, 54974463.04630629], [1709, 55404533.06087942], [1726, 55316304.695168346], [1766, 55588847.36277981], [1770, 55711653.6193069], [1776, 55635967.849223286], [1801, 55857133.82825839], [1937, 56076247.273349956], [1960, 55971522.87008585], [1996, 56434628.542863145], [2029, 56782847.85226863]]

View file

@ -1 +1 @@
[[1662, 44185599.95253766], [1672, 44048383.99999999], [1687, 44312575.95267366], [1691, 44177407.95252886]]
[[1662, 44185599.95253766], [1672, 44048383.99999999], [1687, 44312575.95267366], [1691, 44177407.95252886], [1709, 44312575.95267366], [1726, 44314624.0], [1766, 44316671.95267803], [1770, 44318719.81072088], [1776, 44273651.87380721], [1801, 44453888.0], [1937, 44744704.0], [1960, 44838912.00000001], [1996, 44969983.99999999], [2029, 44847103.812950954]]

View file

@ -1 +1 @@
[[1662, 44187648.0], [1672, 44052479.99999999], [1687, 44312575.95267366], [1691, 44179455.99999999]]
[[1662, 44187648.0], [1672, 44052479.99999999], [1687, 44312575.95267366], [1691, 44179455.99999999], [1709, 44312575.95267366], [1726, 44314624.0], [1766, 44312575.95267366], [1770, 44316671.95267803], [1776, 44437504.0], [1801, 44449792.0], [1937, 44744704.0], [1960, 44900304.17220587], [1996, 44974080.0], [2029, 44984319.953380376]]

View file

@ -1 +1 @@
[[1662, 0.2582970915627115], [1672, 0.2609375697890752], [1687, 0.2585333418012986], [1691, 0.2605245701455466]]
[[1662, 0.2582970915627115], [1672, 0.2609375697890752], [1687, 0.2585333418012986], [1691, 0.2605245701455466], [1709, 0.26615604836262874], [1726, 0.25886008645727265], [1766, 0.2656287982807661], [1770, 0.2675580766089799], [1776, 0.2644825926606367], [1801, 0.26856188100049755], [1937, 0.2700264321932048], [1960, 0.2711075492537049], [1996, 0.28189867248766043], [2029, 0.29481258851469266]]

View file

@ -1 +1 @@
[[1662, 0.144605946222714], [1672, 0.14711376894836906], [1687, 0.14434992731884352], [1691, 0.14638104217028877]]
[[1662, 0.144605946222714], [1672, 0.14711376894836906], [1687, 0.14434992731884352], [1691, 0.14638104217028877], [1709, 0.1508107336447194], [1726, 0.14754149544768042], [1766, 0.15304096778650703], [1770, 0.1536390943522132], [1776, 0.15209353551720362], [1801, 0.15356938175949056], [1937, 0.15690951925702573], [1960, 0.15706997937098616], [1996, 0.16911758076913888], [2029, 0.17625829701058932]]

View file

@ -1 +1 @@
[[1662, 0.004713162243622524], [1672, 0.004881543738505435], [1687, 0.004732102104161917], [1691, 0.00488069732533968]]
[[1662, 0.004713162243622524], [1672, 0.004881543738505435], [1687, 0.004732102104161917], [1691, 0.00488069732533968], [1709, 0.004839972328668506], [1726, 0.004672193982353972], [1766, 0.004766899700580667], [1770, 0.004898703707479391], [1776, 0.004767500868992566], [1801, 0.004852423669122516], [1937, 0.004924660359490744], [1960, 0.004810360631940079], [1996, 0.004998322871483767], [2029, 0.004880129761791827]]

View file

@ -1 +1 @@
[[1662, 0.0005386441936864901], [1672, 0.0005576844109755481], [1687, 0.0005459711425132094], [1691, 0.0005519974567116778]]
[[1662, 0.0005386441936864901], [1672, 0.0005576844109755481], [1687, 0.0005459711425132094], [1691, 0.0005519974567116778], [1709, 0.0005728459938648165], [1726, 0.0005428729701593198], [1766, 0.0005836611719634209], [1770, 0.0005880105852110292], [1776, 0.0005786218553806627], [1801, 0.0005751723828193878], [1937, 0.0005916876201898046], [1960, 0.000594381138510516], [1996, 0.0006163559143381376], [2029, 0.0006119567036345985]]

View file

@ -1 +1 @@
[[1662, 0.21383238744211777], [1672, 0.22221634409189991], [1687, 0.21410434591886193], [1691, 0.2203609725843055]]
[[1662, 0.21383238744211777], [1672, 0.22221634409189991], [1687, 0.21410434591886193], [1691, 0.2203609725843055], [1709, 0.22018038637622225], [1726, 0.2149156309515977], [1766, 0.2198640308272821], [1770, 0.22362192103085216], [1776, 0.21863830924562072], [1801, 0.22295218072522197], [1937, 0.22573752762853083], [1960, 0.22581596577171012], [1996, 0.22995210340856065], [2029, 0.2285231418957897]]

File diff suppressed because one or more lines are too long

View file

@ -1,4 +1,4 @@
{
"asv-version": "0.6.4",
"timestamp": 1742766051964
"timestamp": 1753049912703
}

View file

@ -1 +1 @@
{"regressions": []}
{"regressions": [["Components vs Django.timeraw_render_lg_subsequent('django')", "graphs/branch-master/django-5.1/djc-core-html-parser/machine-ci-linux/python-3.13/Components vs Django.timeraw_render_lg_subsequent.json", {}, 0, 0.04362213900003553, 0.03327357099999517, [[1691, 1709, 0.03327357099999517, 0.03723830100000214], [1801, 1937, 0.03723830100000214, 0.04362213900003553]]]]}

View file

@ -1,2 +1,8 @@
<?xml version='1.0' encoding='utf-8'?>
<feed xmlns="http://www.w3.org/2005/Atom"><id>tag:django-components.asv,1970-01-01:/cddbdcca8b398afd301fbfc73cc4d51103d4e3059c0e6b938d4c467ad3d1aa25</id><author><name>Airspeed Velocity</name></author><title xml:lang="en">django-components performance regressions</title><updated>2025-03-23T21:40:51Z</updated></feed>
<feed xmlns="http://www.w3.org/2005/Atom"><id>tag:django-components.asv,1970-01-01:/cddbdcca8b398afd301fbfc73cc4d51103d4e3059c0e6b938d4c467ad3d1aa25</id><author><name>Airspeed Velocity</name></author><title xml:lang="en">django-components performance regressions</title><updated>2025-06-04T22:41:21Z</updated><entry><id>tag:django-components.asv,2025-06-04:/38b868a38890eb5bccfd51983abf3a15cedf2846ef6cf2452e941122dbde2bde</id><title xml:lang="en">17.14% Components vs Django.timeraw_render_lg_subsequent('django')</title><updated>2025-06-04T22:41:21Z</updated><link href="index.html#Components vs Django.timeraw_render_lg_subsequent?p-renderer=%27django%27&amp;commits=4c909486069f3c3c8ee7915239174f820f081da4-7b24b86f4a836c697acba926d9d6602afa45418d" /><content xml:lang="en" type="html">&lt;a href="index.html#Components vs Django.timeraw_render_lg_subsequent?p-renderer=%27django%27&amp;commits=4c909486069f3c3c8ee7915239174f820f081da4-7b24b86f4a836c697acba926d9d6602afa45418d"&gt;17.14% regression&lt;/a&gt; on 2025-06-04 22:35:21 in commits &lt;a href="#4c909486069f3c3c8ee7915239174f820f081da4"&gt;4c909486...7b24b86f&lt;/a&gt;.&lt;br&gt;
New value: 43.6ms, old value: 37.2ms.&lt;br&gt;
Latest value: 43.6ms (31.10% worse
than best value 33.3ms).</content></entry><entry><id>tag:django-components.asv,2025-03-31:/7a13128cbc4d175ca09ebda40e8a303789275bd84b00a5d496cfa08a26ad2f8b</id><title xml:lang="en">11.92% Components vs Django.timeraw_render_lg_subsequent('django')</title><updated>2025-03-31T14:16:53Z</updated><link href="index.html#Components vs Django.timeraw_render_lg_subsequent?p-renderer=%27django%27&amp;commits=42818ad6ffb47bd650d8a379b84c3d48394f9f77-a6455d70f6c28ddbd4be8e58902f6cbc101e5ff3" /><content xml:lang="en" type="html">&lt;a href="index.html#Components vs Django.timeraw_render_lg_subsequent?p-renderer=%27django%27&amp;commits=42818ad6ffb47bd650d8a379b84c3d48394f9f77-a6455d70f6c28ddbd4be8e58902f6cbc101e5ff3"&gt;11.92% regression&lt;/a&gt; on 2025-03-31 14:10:42 in commits &lt;a href="#42818ad6ffb47bd650d8a379b84c3d48394f9f77"&gt;42818ad6...a6455d70&lt;/a&gt;.&lt;br&gt;
New value: 37.2ms, old value: 33.3ms.&lt;br&gt;
Latest value: 43.6ms (31.10% worse
than best value 33.3ms).</content></entry></feed>

View file

@ -5,9 +5,10 @@ nav:
- Prop drilling and provide / inject: provide_inject.md
- Lifecycle hooks: hooks.md
- Registering components: component_registry.md
- Typing and validation: typing_and_validation.md
- Component caching: component_caching.md
- Component context and scope: component_context_scope.md
- Custom template tags: template_tags.md
- Tag formatters: tag_formatter.md
- Tag formatters: tag_formatters.md
- Extensions: extensions.md
- Testing: testing.md
- Authoring component libraries: authoring_component_libraries.md
- Component libraries: component_libraries.md

View file

@ -0,0 +1,181 @@
Component caching allows you to store the rendered output of a component. Next time the component is rendered
with the same input, the cached output is returned instead of re-rendering the component.
This is particularly useful for components that are expensive to render or do not change frequently.
!!! info
Component caching uses [Django's cache framework](https://docs.djangoproject.com/en/5.2/topics/cache/),
so you can use any cache backend that is supported by Django.
### Enabling caching
Caching is disabled by default.
To enable caching for a component, set [`Component.Cache.enabled`](../../reference/api.md#django_components.ComponentCache.enabled) to `True`:
```python
from django_components import Component
class MyComponent(Component):
class Cache:
enabled = True
```
### Time-to-live (TTL)
You can specify a time-to-live (TTL) for the cache entry with [`Component.Cache.ttl`](../../reference/api.md#django_components.ComponentCache.ttl), which determines how long the entry remains valid. The TTL is specified in seconds.
```python
class MyComponent(Component):
class Cache:
enabled = True
ttl = 60 * 60 * 24 # 1 day
```
- If `ttl > 0`, entries are cached for the specified number of seconds.
- If `ttl = -1`, entries are cached indefinitely.
- If `ttl = 0`, entries are not cached.
- If `ttl = None`, the default TTL is used.
### Custom cache name
Since component caching uses Django's cache framework, you can specify a custom cache name with [`Component.Cache.cache_name`](../../reference/api.md#django_components.ComponentCache.cache_name) to use a different cache backend:
```python
class MyComponent(Component):
class Cache:
enabled = True
cache_name = "my_cache"
```
### Cache key generation
By default, the cache key is generated based on the component's input (args and kwargs). So the following two calls would generate separate entries in the cache:
```py
MyComponent.render(name="Alice")
MyComponent.render(name="Bob")
```
However, you have full control over the cache key generation. As such, you can:
- Cache the component on all inputs (default)
- Cache the component on particular inputs
- Cache the component irrespective of the inputs
To achieve that, you can override
the [`Component.Cache.hash()`](../../reference/api.md#django_components.ComponentCache.hash)
method to customize how arguments are hashed into the cache key.
```python
class MyComponent(Component):
class Cache:
enabled = True
def hash(self, *args, **kwargs):
return f"{json.dumps(args)}:{json.dumps(kwargs)}"
```
For even more control, you can override other methods available on the [`ComponentCache`](../../reference/api.md#django_components.ComponentCache) class.
!!! warning
The default implementation of `Cache.hash()` simply serializes the input into a string.
As such, it might not be suitable if you need to hash complex objects like Models.
### Caching slots
By default, the cache key is generated based ONLY on the args and kwargs.
To cache the component based on the slots, set [`Component.Cache.include_slots`](../../reference/api.md#django_components.ComponentCache.include_slots) to `True`:
```python
class MyComponent(Component):
class Cache:
enabled = True
include_slots = True
```
with `include_slots = True`, the cache key will be generated also based on the given slots.
As such, the following two calls would generate separate entries in the cache:
```django
{% component "my_component" position="left" %}
Hello, Alice
{% endcomponent %}
{% component "my_component" position="left" %}
Hello, Bob
{% endcomponent %}
```
Same when using [`Component.render()`](../../reference/api.md#django_components.Component.render) with string slots:
```py
MyComponent.render(
kwargs={"position": "left"},
slots={"content": "Hello, Alice"}
)
MyComponent.render(
kwargs={"position": "left"},
slots={"content": "Hello, Bob"}
)
```
!!! warning
Passing slots as functions to cached components with `include_slots=True` will raise an error.
```py
MyComponent.render(
kwargs={"position": "left"},
slots={"content": lambda ctx: "Hello, Alice"}
)
```
!!! warning
Slot caching DOES NOT account for context variables within
the [`{% fill %}`](../../reference/template_tags.md#fill) tag.
For example, the following two cases will be treated as the same entry:
```django
{% with my_var="foo" %}
{% component "mycomponent" name="foo" %}
{{ my_var }}
{% endcomponent %}
{% endwith %}
{% with my_var="bar" %}
{% component "mycomponent" name="bar" %}
{{ my_var }}
{% endcomponent %}
{% endwith %}
```
Currently it's impossible to capture used variables. This will be addressed in v2.
Read more about it in [django-components/#1164](https://github.com/django-components/django-components/issues/1164).
### Example
Here's a complete example of a component with caching enabled:
```python
from django_components import Component
class MyComponent(Component):
template = "Hello, {{ name }}"
class Cache:
enabled = True
ttl = 300 # Cache for 5 minutes
cache_name = "my_cache"
def get_template_data(self, args, kwargs, slots, context):
return {"name": kwargs["name"]}
```
In this example, the component's rendered output is cached for 5 minutes using the `my_cache` backend.

View file

@ -12,7 +12,7 @@ NOTE: `{% csrf_token %}` tags need access to the top-level context, and they wil
If you find yourself using the `only` modifier often, you can set the [context_behavior](#context-behavior) option to `"isolated"`, which automatically applies the `only` modifier. This is useful if you want to make sure that components don't accidentally access the outer context.
Components can also access the outer context in their context methods like `get_context_data` by accessing the property `self.outer_context`.
Components can also access the outer context in their context methods like `get_template_data` by accessing the property `self.outer_context`.
## Example of Accessing Outer Context
@ -22,14 +22,14 @@ Components can also access the outer context in their context methods like `get_
</div>
```
Assuming that the rendering context has variables such as `date`, you can use `self.outer_context` to access them from within `get_context_data`. Here's how you might implement it:
Assuming that the rendering context has variables such as `date`, you can use `self.outer_context` to access them from within `get_template_data`. Here's how you might implement it:
```python
class Calender(Component):
...
def get_context_data(self):
def get_template_data(self, args, kwargs, slots, context):
outer_field = self.outer_context["date"]
return {
"date": outer_fields,
@ -53,10 +53,10 @@ This has two modes:
you can access are a union of:
- All the variables that were OUTSIDE the fill tag, including any\
[`{% with %}`](https://docs.djangoproject.com/en/5.1/ref/templates/builtins/#with) tags.
- Any loops ([`{% for ... %}`](https://docs.djangoproject.com/en/5.1/ref/templates/builtins/#cycle))
[`{% with %}`](https://docs.djangoproject.com/en/5.2/ref/templates/builtins/#with) tags.
- Any loops ([`{% for ... %}`](https://docs.djangoproject.com/en/5.2/ref/templates/builtins/#cycle))
that the `{% fill %}` tag is part of.
- Data returned from [`Component.get_context_data()`](../../../reference/api#django_components.Component.get_context_data)
- Data returned from [`Component.get_template_data()`](../../../reference/api#django_components.Component.get_template_data)
of the component that owns the fill tag.
- `"isolated"`
@ -67,14 +67,14 @@ This has two modes:
Inside the [`{% fill %}`](../../../reference/template_tags#fill) tag, you can ONLY access variables from 2 places:
- Any loops ([`{% for ... %}`](https://docs.djangoproject.com/en/5.1/ref/templates/builtins/#cycle))
- Any loops ([`{% for ... %}`](https://docs.djangoproject.com/en/5.2/ref/templates/builtins/#cycle))
that the `{% fill %}` tag is part of.
- [`Component.get_context_data()`](../../../reference/api#django_components.Component.get_context_data)
- [`Component.get_template_data()`](../../../reference/api#django_components.Component.get_template_data)
of the component which defined the template (AKA the "root" component).
!!! warning
Notice that the component whose `get_context_data()` we use inside
Notice that the component whose `get_template_data()` we use inside
[`{% fill %}`](../../../reference/template_tags#fill)
is NOT the same across the two modes!
@ -93,10 +93,10 @@ This has two modes:
"""
```
- `"django"` - `my_var` has access to data from `get_context_data()` of both `Inner` and `Outer`.
- `"django"` - `my_var` has access to data from `get_template_data()` of both `Inner` and `Outer`.
If there are variables defined in both, then `Inner` overshadows `Outer`.
- `"isolated"` - `my_var` has access to data from `get_context_data()` of ONLY `Outer`.
- `"isolated"` - `my_var` has access to data from `get_template_data()` of ONLY `Outer`.
### Example "django"
@ -115,11 +115,11 @@ class RootComp(Component):
{% endwith %}
"""
def get_context_data(self):
def get_template_data(self, args, kwargs, slots, context):
return { "my_var": 123 }
```
Then if [`get_context_data()`](../../../reference/api#django_components.Component.get_context_data)
Then if [`get_template_data()`](../../../reference/api#django_components.Component.get_template_data)
of the component `"my_comp"` returns following data:
```py
@ -154,11 +154,11 @@ class RootComp(Component):
{% endwith %}
"""
def get_context_data(self):
def get_template_data(self, args, kwargs, slots, context):
return { "my_var": 123 }
```
Then if [`get_context_data()`](../../../reference/api#django_components.Component.get_context_data)
Then if [`get_template_data()`](../../../reference/api#django_components.Component.get_template_data)
of the component `"my_comp"` returns following data:
```py
@ -172,10 +172,10 @@ Then the template will be rendered as:
# cheese
```
Because variables `"my_var"` and `"cheese"` are searched only inside `RootComponent.get_context_data()`.
Because variables `"my_var"` and `"cheese"` are searched only inside `RootComponent.get_template_data()`.
But since `"cheese"` is not defined there, it's empty.
!!! info
Notice that the variables defined with the [`{% with %}`](https://docs.djangoproject.com/en/5.1/ref/templates/builtins/#with)
Notice that the variables defined with the [`{% with %}`](https://docs.djangoproject.com/en/5.2/ref/templates/builtins/#with)
tag are ignored inside the [`{% fill %}`](../../../reference/template_tags#fill) tag with the `"isolated"` mode.

View file

@ -23,13 +23,13 @@ For live examples, see the [Community examples](../../overview/community.md#comm
|-- mytags.py
```
2. Create custom [`Library`](https://docs.djangoproject.com/en/5.1/howto/custom-template-tags/#how-to-create-custom-template-tags-and-filters)
2. Create custom [`Library`](https://docs.djangoproject.com/en/5.2/howto/custom-template-tags/#how-to-create-custom-template-tags-and-filters)
and [`ComponentRegistry`](django_components.component_registry.ComponentRegistry) instances in `mytags.py`
This will be the entrypoint for using the components inside Django templates.
Remember that Django requires the [`Library`](https://docs.djangoproject.com/en/5.1/howto/custom-template-tags/#how-to-create-custom-template-tags-and-filters)
instance to be accessible under the `register` variable ([See Django docs](https://docs.djangoproject.com/en/dev/howto/custom-template-tags)):
Remember that Django requires the [`Library`](https://docs.djangoproject.com/en/5.2/howto/custom-template-tags/#how-to-create-custom-template-tags-and-filters)
instance to be accessible under the `register` variable ([See Django docs](https://docs.djangoproject.com/en/5.2/howto/custom-template-tags)):
```py
from django.template import Library
@ -101,35 +101,30 @@ For live examples, see the [Community examples](../../overview/community.md#comm
It's also a good idea to have a common prefix for your components, so they can be easily distinguished from users' components. In the example below, we use the prefix `my_` / `My`.
```djc_py
from typing import Dict, NotRequired, Optional, Tuple, TypedDict
from django_components import Component, SlotFunc, register, types
from typing import NamedTuple, Optional
from django_components import Component, SlotInput, register, types
from myapp.templatetags.mytags import comp_registry
# Define the types
class EmptyDict(TypedDict):
pass
type MyMenuArgs = Tuple[int, str]
class MyMenuSlots(TypedDict):
default: NotRequired[Optional[SlotFunc[EmptyDict]]]
class MyMenuProps(TypedDict):
vertical: NotRequired[bool]
klass: NotRequired[str]
style: NotRequired[str]
# Define the component
# NOTE: Don't forget to set the `registry`!
@register("my_menu", registry=comp_registry)
class MyMenu(Component[MyMenuArgs, MyMenuProps, MyMenuSlots, Any, Any, Any]):
def get_context_data(
self,
*args,
attrs: Optional[Dict] = None,
):
class MyMenu(Component):
# Define the types
class Args(NamedTuple):
size: int
text: str
class Kwargs(NamedTuple):
vertical: Optional[bool] = None
klass: Optional[str] = None
style: Optional[str] = None
class Slots(NamedTuple):
default: Optional[SlotInput] = None
def get_template_data(self, args: Args, kwargs: Kwargs, slots: Slots, context: Context):
attrs = ...
return {
"attrs": attrs,
}
@ -153,7 +148,7 @@ For live examples, see the [Community examples](../../overview/community.md#comm
Since you, as the library author, are not in control of the file system, it is recommended to load the components manually.
We recommend doing this in the [`AppConfig.ready()`](https://docs.djangoproject.com/en/5.1/ref/applications/#django.apps.AppConfig.ready)
We recommend doing this in the [`AppConfig.ready()`](https://docs.djangoproject.com/en/5.2/ref/applications/#django.apps.AppConfig.ready)
hook of your `apps.py`:
```py
@ -175,7 +170,7 @@ For live examples, see the [Community examples](../../overview/community.md#comm
```
Note that you can also include any other startup logic within
[`AppConfig.ready()`](https://docs.djangoproject.com/en/5.1/ref/applications/#django.apps.AppConfig.ready).
[`AppConfig.ready()`](https://docs.djangoproject.com/en/5.2/ref/applications/#django.apps.AppConfig.ready).
And that's it! The next step is to publish it.
@ -190,7 +185,7 @@ django_components uses the [`build`](https://build.pypa.io/en/stable/) utility t
python -m build --sdist --wheel --outdir dist/ .
```
And to publish to PyPI, you can use [`twine`](https://docs.djangoproject.com/en/5.1/ref/applications/#django.apps.AppConfig.ready)
And to publish to PyPI, you can use [`twine`](https://docs.djangoproject.com/en/5.2/ref/applications/#django.apps.AppConfig.ready)
([See Python user guide](https://packaging.python.org/en/latest/tutorials/packaging-projects/#uploading-the-distribution-archives))
```bash
@ -224,7 +219,7 @@ After the package has been published, all that remains is to install it in other
]
```
3. Optionally add the template tags to the [`builtins`](https://docs.djangoproject.com/en/5.1/topics/templates/#django.template.backends.django.DjangoTemplates),
3. Optionally add the template tags to the [`builtins`](https://docs.djangoproject.com/en/5.2/topics/templates/#django.template.backends.django.DjangoTemplates),
so you don't have to call `{% load mytags %}` in every template:
```python

View file

@ -12,9 +12,9 @@ class Calendar(Component):
template_file = "template.html"
# This component takes one parameter, a date string to show in the template
def get_context_data(self, date):
def get_template_data(self, args, kwargs, slots, context):
return {
"date": date,
"date": kwargs["date"],
}
```

View file

@ -6,7 +6,11 @@ Django-components functionality can be extended with "extensions". Extensions al
- Add new attributes and methods to the components under an extension-specific nested class.
- Define custom commands that can be executed via the Django management command interface.
## Setting up extensions
## Live examples
- [djc-ext-pydantic](https://github.com/django-components/djc-ext-pydantic)
## Install extensions
Extensions are configured in the Django settings under [`COMPONENTS.extensions`](../../../reference/settings#django_components.app_settings.ComponentsSettings.extensions).
@ -18,7 +22,7 @@ Extensions can be set by either as an import string or by passing in a class:
class MyExtension(ComponentExtension):
name = "my_extension"
class ExtensionClass(ComponentExtension.ExtensionClass):
class ComponentConfig(ExtensionComponentConfig):
...
COMPONENTS = ComponentsSettings(
@ -38,16 +42,22 @@ Extensions can define methods to hook into lifecycle events, such as:
- Un/registering a component
- Creating or deleting a registry
- Pre-processing data passed to a component on render
- Post-processing data returned from [`get_context_data()`](../../../reference/api#django_components.Component.get_context_data)
- Post-processing data returned from [`get_template_data()`](../../../reference/api#django_components.Component.get_template_data)
and others.
See the full list in [Extension Hooks Reference](../../../reference/extension_hooks).
## Configuring extensions per component
## Per-component configuration
Each extension has a corresponding nested class within the [`Component`](../../../reference/api#django_components.Component) class. These allow
to configure the extensions on a per-component basis.
E.g.:
- `"view"` extension -> [`Component.View`](../../../reference/api#django_components.Component.View)
- `"cache"` extension -> [`Component.Cache`](../../../reference/api#django_components.Component.Cache)
- `"defaults"` extension -> [`Component.Defaults`](../../../reference/api#django_components.Component.Defaults)
!!! note
**Accessing the component instance from inside the nested classes:**
@ -57,31 +67,32 @@ to configure the extensions on a per-component basis.
```python
class MyTable(Component):
class View:
class MyExtension:
def get(self, request):
# `self.component` points to the instance of `MyTable` Component.
return self.component.get(request)
return self.component.render_to_response(request=request)
```
### Example: Component as View
The [Components as Views](../../fundamentals/components_as_views) feature is actually implemented as an extension
The [Components as Views](../../fundamentals/component_views_urls) feature is actually implemented as an extension
that is configured by a `View` nested class.
You can override the `get`, `post`, etc methods to customize the behavior of the component as a view:
You can override the `get()`, `post()`, etc methods to customize the behavior of the component as a view:
```python
class MyTable(Component):
class View:
def get(self, request):
return self.component.get(request)
return self.component_class.render_to_response(request=request)
def post(self, request):
return self.component.post(request)
return self.component_class.render_to_response(request=request)
...
```
<!-- TODO - LINK TO IT ONCE RELEASED -->
### Example: Storybook integration
The Storybook integration (work in progress) is an extension that is configured by a `Storybook` nested class.
@ -93,12 +104,12 @@ JSON file from the component.
class MyTable(Component):
class Storybook:
def title(self):
return self.component.__class__.__name__
return self.component_cls.__name__
def parameters(self) -> Parameters:
return {
"server": {
"id": self.component.__class__.__name__,
"id": self.component_cls.__name__,
}
}
@ -108,17 +119,84 @@ class MyTable(Component):
...
```
## Accessing extensions in components
### Extension defaults
Extensions are incredibly flexible, but configuring the same extension for every component can be a pain.
For this reason, django-components allows for extension defaults. This is like setting the extension config for every component.
To set extension defaults, use the [`COMPONENTS.extensions_defaults`](../../../reference/settings#django_components.app_settings.ComponentsSettings.extensions_defaults) setting.
The `extensions_defaults` setting is a dictionary where the key is the extension name and the value is a dictionary of config attributes:
```python
COMPONENTS = ComponentsSettings(
extensions=[
"my_extension.MyExtension",
"storybook.StorybookExtension",
],
extensions_defaults={
"my_extension": {
"key": "value",
},
"view": {
"public": True,
},
"cache": {
"ttl": 60,
},
"storybook": {
"title": lambda self: self.component_cls.__name__,
},
},
)
```
Which is equivalent to setting the following for every component:
```python
class MyTable(Component):
class MyExtension:
key = "value"
class View:
public = True
class Cache:
ttl = 60
class Storybook:
def title(self):
return self.component_cls.__name__
```
!!! info
If you define an attribute as a function, it is like defining a method on the extension class.
E.g. in the example above, `title` is a method on the `Storybook` extension class.
As the name suggests, these are defaults, and so you can still selectively override them on a per-component basis:
```python
class MyTable(Component):
class View:
public = False
```
### Extensions in component instances
Above, we've configured extensions `View` and `Storybook` for the `MyTable` component.
You can access the instances of these extension classes in the component instance.
Extensions are available under their names (e.g. `self.view`, `self.storybook`).
For example, the View extension is available as `self.view`:
```python
class MyTable(Component):
def get_context_data(self, request):
def get_template_data(self, args, kwargs, slots, context):
# `self.view` points to the instance of `View` extension.
return {
"view": self.view,
@ -129,30 +207,30 @@ And the Storybook extension is available as `self.storybook`:
```python
class MyTable(Component):
def get_context_data(self, request):
def get_template_data(self, args, kwargs, slots, context):
# `self.storybook` points to the instance of `Storybook` extension.
return {
"title": self.storybook.title(),
}
```
Thus, you can use extensions to add methods or attributes that will be available to all components
in their component context.
## Writing extensions
Creating extensions in django-components involves defining a class that inherits from
[`ComponentExtension`](../../../reference/api/#django_components.ComponentExtension).
This class can implement various lifecycle hooks and define new attributes or methods to be added to components.
### Defining an extension
### Extension class
To create an extension, define a class that inherits from [`ComponentExtension`](../../../reference/api/#django_components.ComponentExtension)
and implement the desired hooks.
- Each extension MUST have a `name` attribute. The name MUST be a valid Python identifier.
- The extension MAY implement any of the [hook methods](../../../reference/extension_hooks).
- Each hook method receives a context object with relevant data.
- The extension may implement any of the [hook methods](../../../reference/extension_hooks).
Each hook method receives a context object with relevant data.
- Extension may own [URLs](#extension-urls) or [CLI commands](#extension-commands).
```python
from django_components.extension import ComponentExtension, OnComponentClassCreatedContext
@ -165,9 +243,18 @@ class MyExtension(ComponentExtension):
ctx.component_cls.my_attr = "my_value"
```
### Defining the extension class
!!! warning
In previous sections we've seen the `View` and `Storybook` extensions classes that were nested within the `Component` class:
The `name` attribute MUST be unique across all extensions.
Moreover, the `name` attribute MUST NOT conflict with existing Component class API.
So if you name an extension `render`, it will conflict with the [`render()`](../../../reference/api/#django_components.Component.render) method of the `Component` class.
### Component config
In previous sections we've seen the `View` and `Storybook` extensions classes that were nested
within the [`Component`](../../../reference/api/#django_components.Component) class:
```python
class MyComponent(Component):
@ -180,32 +267,30 @@ class MyComponent(Component):
These can be understood as component-specific overrides or configuration.
The nested extension classes like `View` or `Storybook` will actually subclass from a base extension
class as defined on the [`ComponentExtension.ExtensionClass`](../../../reference/api/#django_components.ComponentExtension.ExtensionClass).
Whether it's `Component.View` or `Component.Storybook`, their respective extensions
defined how these nested classes will behave.
This is how extensions define the "default" behavior of their nested extension classes.
For example, the `View` base extension class defines the handlers for GET, POST, etc:
For example, the View extension defines the API that users may override in `ViewExtension.ComponentConfig`:
```python
from django_components.extension import ComponentExtension
from django_components.extension import ComponentExtension, ExtensionComponentConfig
class ViewExtension(ComponentExtension):
name = "view"
# The default behavior of the `View` extension class.
class ExtensionClass(ComponentExtension.ExtensionClass):
class ComponentConfig(ExtensionComponentConfig):
def get(self, request):
return self.component.get(request)
raise NotImplementedError("You must implement the `get` method.")
def post(self, request):
return self.component.post(request)
raise NotImplementedError("You must implement the `post` method.")
...
```
In any component that then defines a nested `View` extension class, the `View` extension class will actually
subclass from the `ViewExtension.ExtensionClass` class.
In any component that then defines a nested `Component.View` extension class, the resulting `View` class
will actually subclass from the `ViewExtension.ComponentConfig` class.
In other words, when you define a component like this:
@ -217,11 +302,11 @@ class MyTable(Component):
...
```
It will actually be implemented as if the `View` class subclassed from base class `ViewExtension.ExtensionClass`:
Behind the scenes it is as if you defined the following:
```python
class MyTable(Component):
class View(ViewExtension.ExtensionClass):
class View(ViewExtension.ComponentConfig):
def get(self, request):
# Do something
...
@ -229,13 +314,13 @@ class MyTable(Component):
!!! warning
When writing an extension, the `ExtensionClass` MUST subclass the base class [`ComponentExtension.ExtensionClass`](../../../reference/api/#django_components.ComponentExtension.ExtensionClass).
When writing an extension, the `ComponentConfig` MUST subclass the base class [`ExtensionComponentConfig`](../../../reference/api/#django_components.ExtensionComponentConfig).
This base class ensures that the extension class will have access to the component instance.
### Registering extensions
### Install your extension
Once the extension is defined, it needs to be registered in the Django settings to be used by the application.
Once the extension is defined, it needs to be installed in the Django settings to be used by the application.
Extensions can be given either as an extension class, or its import string:
@ -271,30 +356,30 @@ To tie it all together, here's an example of a custom logging extension that log
```python
from django_components.extension import (
ComponentExtension,
ExtensionComponentConfig,
OnComponentClassCreatedContext,
OnComponentClassDeletedContext,
OnComponentInputContext,
)
class ColorLoggerExtensionClass(ComponentExtension.ExtensionClass):
color: str
class ColorLoggerExtension(ComponentExtension):
name = "color_logger"
# All `Component.ColorLogger` classes will inherit from this class.
ExtensionClass = ColorLoggerExtensionClass
class ComponentConfig(ExtensionComponentConfig):
color: str
# These hooks don't have access to the Component instance, only to the Component class,
# so we access the color as `Component.ColorLogger.color`.
def on_component_class_created(self, ctx: OnComponentClassCreatedContext) -> None:
# These hooks don't have access to the Component instance,
# only to the Component class, so we access the color
# as `Component.ColorLogger.color`.
def on_component_class_created(self, ctx: OnComponentClassCreatedContext):
log.info(
f"Component {ctx.component_cls} created.",
color=ctx.component_cls.ColorLogger.color,
)
def on_component_class_deleted(self, ctx: OnComponentClassDeletedContext) -> None:
def on_component_class_deleted(self, ctx: OnComponentClassDeletedContext):
log.info(
f"Component {ctx.component_cls} deleted.",
color=ctx.component_cls.ColorLogger.color,
@ -302,7 +387,7 @@ class ColorLoggerExtension(ComponentExtension):
# This hook has access to the Component instance, so we access the color
# as `self.component.color_logger.color`.
def on_component_input(self, ctx: OnComponentInputContext) -> None:
def on_component_input(self, ctx: OnComponentInputContext):
log.info(
f"Rendering component {ctx.component_cls}.",
color=ctx.component.color_logger.color,
@ -320,7 +405,7 @@ COMPONENTS = {
}
```
Once registered, in any component, you can define a `ColorLogger` attribute:
Once installed, in any component, you can define a `ColorLogger` attribute:
```python
class MyComponent(Component):
@ -337,45 +422,77 @@ django-components provides a few utility functions to help with writing extensio
- [`all_components()`](../../../reference/api#django_components.all_components) - returns a list of all created component classes.
- [`all_registries()`](../../../reference/api#django_components.all_registries) - returns a list of all created registry instances.
### Accessing the component class from within an extension
### Access component class
When you are writing the extension class that will be nested inside a Component class, e.g.
You can access the owner [`Component`](../../../reference/api/#django_components.Component) class (`MyTable`) from within methods
of the extension class (`MyExtension`) by using
the [`component_cls`](../../../reference/api/#django_components.ExtensionComponentConfig.component_cls) attribute:
```py
class MyTable(Component):
class MyExtension:
def some_method(self):
...
print(self.component_cls)
```
You can access the owner Component class (`MyTable`) from within methods of the extension class (`MyExtension`) by using the `component_class` attribute:
```py
class MyTable(Component):
class MyExtension:
def some_method(self):
print(self.component_class)
```
Here is how the `component_class` attribute may be used with our `ColorLogger`
Here is how the `component_cls` attribute may be used with our `ColorLogger`
extension shown above:
```python
class ColorLoggerExtensionClass(ComponentExtension.ExtensionClass):
class ColorLoggerComponentConfig(ExtensionComponentConfig):
color: str
def log(self, msg: str) -> None:
print(f"{self.component_class.name}: {msg}")
print(f"{self.component_cls.__name__}: {msg}")
class ColorLoggerExtension(ComponentExtension):
name = "color_logger"
# All `Component.ColorLogger` classes will inherit from this class.
ExtensionClass = ColorLoggerExtensionClass
ComponentConfig = ColorLoggerComponentConfig
```
## Extension Commands
### Pass slot metadata
When a slot is passed to a component, it is copied, so that the original slot is not modified
with rendering metadata.
Therefore, don't use slot's identity to associate metadata with the slot:
```py
# ❌ Don't do this:
slots_cache = {}
class LoggerExtension(ComponentExtension):
name = "logger"
def on_component_input(self, ctx: OnComponentInputContext):
for slot in ctx.component.slots.values():
slots_cache[id(slot)] = {"some": "metadata"}
```
Instead, use the [`Slot.extra`](../../../reference/api#django_components.Slot.extra) attribute,
which is copied from the original slot:
```python
# ✅ Do this:
class LoggerExtension(ComponentExtension):
name = "logger"
# Save component-level logger settings for each slot when a component is rendered.
def on_component_input(self, ctx: OnComponentInputContext):
for slot in ctx.component.slots.values():
slot.extra["logger"] = ctx.component.logger
# Then, when a fill is rendered with `{% slot %}`, we can access the logger settings
# from the slot's metadata.
def on_slot_rendered(self, ctx: OnSlotRenderedContext):
logger = ctx.slot.extra["logger"]
logger.log(...)
```
## Extension commands
Extensions in django-components can define custom commands that can be executed via the Django management command interface. This allows for powerful automation and customization capabilities.
@ -392,9 +509,9 @@ Where:
- `my_ext` - is the extension name
- `hello` - is the command name
### Defining Commands
### Define commands
To define a command, subclass from [`ComponentCommand`](../../../reference/api#django_components.ComponentCommand).
To define a command, subclass from [`ComponentCommand`](../../../reference/extension_commands#django_components.ComponentCommand).
This subclass should define:
- `name` - the command's name
@ -416,15 +533,15 @@ class MyExt(ComponentExtension):
commands = [HelloCommand]
```
### Defining Command Arguments and Options
### Define arguments and options
Commands can accept positional arguments and options (e.g. `--foo`), which are defined using the
[`arguments`](../../../reference/api#django_components.ComponentCommand.arguments)
attribute of the [`ComponentCommand`](../../../reference/api#django_components.ComponentCommand) class.
[`arguments`](../../../reference/extension_commands#django_components.ComponentCommand.arguments)
attribute of the [`ComponentCommand`](../../../reference/extension_commands#django_components.ComponentCommand) class.
The arguments are parsed with [`argparse`](https://docs.python.org/3/library/argparse.html)
into a dictionary of arguments and options. These are then available
as keyword arguments to the [`handle`](../../../reference/api#django_components.ComponentCommand.handle)
as keyword arguments to the [`handle`](../../../reference/extension_commands#django_components.ComponentCommand.handle)
method of the command.
```python
@ -470,20 +587,20 @@ python manage.py components ext run my_ext hello John --shout
See the [argparse documentation](https://docs.python.org/3/library/argparse.html) for more information.
django-components defines types as
[`CommandArg`](../../../reference/api#django_components.CommandArg),
[`CommandArgGroup`](../../../reference/api#django_components.CommandArgGroup),
[`CommandSubcommand`](../../../reference/api#django_components.CommandSubcommand),
and [`CommandParserInput`](../../../reference/api#django_components.CommandParserInput)
[`CommandArg`](../../../reference/extension_commands#django_components.CommandArg),
[`CommandArgGroup`](../../../reference/extension_commands#django_components.CommandArgGroup),
[`CommandSubcommand`](../../../reference/extension_commands#django_components.CommandSubcommand),
and [`CommandParserInput`](../../../reference/extension_commands#django_components.CommandParserInput)
to help with type checking.
!!! note
If a command doesn't have the [`handle`](../../../reference/api#django_components.ComponentCommand.handle)
If a command doesn't have the [`handle`](../../../reference/extension_commands#django_components.ComponentCommand.handle)
method defined, the command will print a help message and exit.
### Grouping Arguments
### Argument groups
Arguments can be grouped using [`CommandArgGroup`](../../../reference/api#django_components.CommandArgGroup)
Arguments can be grouped using [`CommandArgGroup`](../../../reference/extension_commands#django_components.CommandArgGroup)
to provide better organization and help messages.
Read more on [argparse argument groups](https://docs.python.org/3/library/argparse.html#argument-groups).
@ -539,12 +656,12 @@ class HelloCommand(ComponentCommand):
Extensions can define subcommands, allowing for more complex command structures.
Subcommands are defined similarly to root commands, as subclasses of
[`ComponentCommand`](../../../reference/api#django_components.ComponentCommand) class.
[`ComponentCommand`](../../../reference/extension_commands#django_components.ComponentCommand) class.
However, instead of defining the subcommands in the
[`commands`](../../../reference/api#django_components.ComponentExtension.commands)
[`commands`](../../../reference/extension_commands#django_components.ComponentExtension.commands)
attribute of the extension, you define them in the
[`subcommands`](../../../reference/api#django_components.ComponentCommand.subcommands)
[`subcommands`](../../../reference/extension_commands#django_components.ComponentCommand.subcommands)
attribute of the parent command:
```python
@ -600,7 +717,7 @@ python manage.py components ext run parent child
python manage.py components ext run parent child --foo --bar
```
### Print command help
### Help message
By default, all commands will print their help message when run with the `--help` / `-h` flag.
@ -610,9 +727,9 @@ python manage.py components ext run my_ext --help
The help message prints out all the arguments and options available for the command, as well as any subcommands.
### Testing Commands
### Testing commands
Commands can be tested using Django's [`call_command()`](https://docs.djangoproject.com/en/5.1/ref/django-admin/#running-management-commands-from-your-code)
Commands can be tested using Django's [`call_command()`](https://docs.djangoproject.com/en/5.2/ref/django-admin/#running-management-commands-from-your-code)
function, which allows you to simulate running the command in tests.
```python
@ -663,7 +780,7 @@ def test_hello_command(self):
Extensions can define custom views and endpoints that can be accessed through the Django application.
To define URLs for an extension, set them in the [`urls`](../../../reference/api#django_components.ComponentExtension.urls) attribute of your [`ComponentExtension`](../../../reference/api#django_components.ComponentExtension) class. Each URL is defined using the [`URLRoute`](../../../reference/api#django_components.URLRoute) class, which specifies the path, handler, and optional name for the route.
To define URLs for an extension, set them in the [`urls`](../../../reference/api#django_components.ComponentExtension.urls) attribute of your [`ComponentExtension`](../../../reference/api#django_components.ComponentExtension) class. Each URL is defined using the [`URLRoute`](../../../reference/extension_urls#django_components.URLRoute) class, which specifies the path, handler, and optional name for the route.
Here's an example of how to define URLs within an extension:
@ -685,17 +802,17 @@ class MyExtension(ComponentExtension):
!!! warning
The [`URLRoute`](../../../reference/api#django_components.URLRoute) objects
The [`URLRoute`](../../../reference/extension_urls#django_components.URLRoute) objects
are different from objects created with Django's
[`django.urls.path()`](https://docs.djangoproject.com/en/5.1/ref/urls/#path).
Do NOT use `URLRoute` objects in Django's [`urlpatterns`](https://docs.djangoproject.com/en/5.1/topics/http/urls/#example)
[`django.urls.path()`](https://docs.djangoproject.com/en/5.2/ref/urls/#path).
Do NOT use `URLRoute` objects in Django's [`urlpatterns`](https://docs.djangoproject.com/en/5.2/topics/http/urls/#example)
and vice versa!
django-components uses a custom [`URLRoute`](../../../reference/api#django_components.URLRoute) class to define framework-agnostic routing rules.
django-components uses a custom [`URLRoute`](../../../reference/extension_urls#django_components.URLRoute) class to define framework-agnostic routing rules.
As of v0.131, `URLRoute` objects are directly converted to Django's `URLPattern` and `URLResolver` objects.
### Accessing Extension URLs
### URL paths
The URLs defined in an extension are available under the path
@ -713,9 +830,9 @@ For example, if you have defined a URL with the path `my-view/<str:name>/` in an
Extensions can also define nested URLs to allow for more complex routing structures.
To define nested URLs, set the [`children`](../../../reference/api#django_components.URLRoute.children)
attribute of the [`URLRoute`](../../../reference/api#django_components.URLRoute) object to
a list of child [`URLRoute`](../../../reference/api#django_components.URLRoute) objects:
To define nested URLs, set the [`children`](../../../reference/extension_urls#django_components.URLRoute.children)
attribute of the [`URLRoute`](../../../reference/extension_urls#django_components.URLRoute) object to
a list of child [`URLRoute`](../../../reference/extension_urls#django_components.URLRoute) objects:
```python
class MyExtension(ComponentExtension):
@ -740,17 +857,17 @@ In this example, the URL
would call the `my_view` handler with the parameter `name` set to `"John"`.
### Passing kwargs and other extra fields to URL routes
### Extra URL data
The [`URLRoute`](../../../reference/api#django_components.URLRoute) class is framework-agnostic,
The [`URLRoute`](../../../reference/extension_urls#django_components.URLRoute) class is framework-agnostic,
so that extensions could be used with non-Django frameworks in the future.
However, that means that there may be some extra fields that Django's
[`django.urls.path()`](https://docs.djangoproject.com/en/5.1/ref/urls/#path)
[`django.urls.path()`](https://docs.djangoproject.com/en/5.2/ref/urls/#path)
accepts, but which are not defined on the `URLRoute` object.
To address this, the [`URLRoute`](../../../reference/api#django_components.URLRoute) object has
an [`extra`](../../../reference/api#django_components.URLRoute.extra) attribute,
To address this, the [`URLRoute`](../../../reference/extension_urls#django_components.URLRoute) object has
an [`extra`](../../../reference/extension_urls#django_components.URLRoute.extra) attribute,
which is a dictionary that can be used to pass any extra kwargs to `django.urls.path()`:
```python

View file

@ -1,58 +1,333 @@
_New in version 0.96_
Component hooks are functions that allow you to intercept the rendering process at specific positions.
Intercept the rendering lifecycle with Component hooks.
Unlike the [extension hooks](../../../reference/extension_hooks/), these are defined directly
on the [`Component`](../../../reference/api#django_components.Component) class.
## Available hooks
- `on_render_before`
### `on_render_before`
```py
def on_render_before(
self: Component,
context: Context,
template: Template
) -> None:
```
```py
def on_render_before(
self: Component,
context: Context,
template: Optional[Template],
) -> None:
```
Hook that runs just before the component's template is rendered.
[`Component.on_render_before`](../../../reference/api#django_components.Component.on_render_before) runs just before the component's template is rendered.
You can use this hook to access or modify the context or the template:
It is called for every component, including nested ones, as part of
the component render lifecycle.
```py
def on_render_before(self, context, template) -> None:
# Insert value into the Context
context["from_on_before"] = ":)"
It receives the [Context](https://docs.djangoproject.com/en/5.2/ref/templates/api/#django.template.Context)
and the [Template](https://docs.djangoproject.com/en/5.2/ref/templates/api/#django.template.Template)
as arguments.
# Append text into the Template
template.nodelist.append(TextNode("FROM_ON_BEFORE"))
```
The `template` argument is `None` if the component has no template.
- `on_render_after`
**Example:**
```py
def on_render_after(
self: Component,
context: Context,
template: Template,
content: str
) -> None | str | SafeString:
```
You can use this hook to access the context or the template:
Hook that runs just after the component's template was rendered.
It receives the rendered output as the last argument.
```py
from django.template import Context, Template
from django_components import Component
You can use this hook to access the context or the template, but modifying
them won't have any effect.
class MyTable(Component):
def on_render_before(self, context: Context, template: Optional[Template]) -> None:
# Insert value into the Context
context["from_on_before"] = ":)"
To override the content that gets rendered, you can return a string or SafeString from this hook:
assert isinstance(template, Template)
```
```py
def on_render_after(self, context, template, content):
# Prepend text to the rendered content
return "Chocolate cookie recipe: " + content
```
!!! warning
## Component hooks example
If you want to pass data to the template, prefer using
[`get_template_data()`](../../../reference/api#django_components.Component.get_template_data)
instead of this hook.
!!! warning
Do NOT modify the template in this hook. The template is reused across renders.
Since this hook is called for every component, this means that the template would be modified
every time a component is rendered.
### `on_render`
_New in version 0.140_
```py
def on_render(
self: Component,
context: Context,
template: Optional[Template],
) -> Union[str, SafeString, OnRenderGenerator, None]:
```
[`Component.on_render`](../../../reference/api#django_components.Component.on_render) does the actual rendering.
You can override this method to:
- Change what template gets rendered
- Modify the context
- Modify the rendered output after it has been rendered
- Handle errors
The default implementation renders the component's
[Template](https://docs.djangoproject.com/en/5.2/ref/templates/api/#django.template.Template)
with the given
[Context](https://docs.djangoproject.com/en/5.2/ref/templates/api/#django.template.Context).
```py
class MyTable(Component):
def on_render(self, context, template):
if template is None:
return None
else:
return template.render(context)
```
The `template` argument is `None` if the component has no template.
#### Modifying rendered template
To change what gets rendered, you can:
- Render a different template
- Render a component
- Return a different string or SafeString
```py
class MyTable(Component):
def on_render(self, context, template):
return "Hello"
```
You can also use [`on_render()`](../../../reference/api#django_components.Component.on_render) as a router,
rendering other components based on the parent component's arguments:
```py
class MyTable(Component):
def on_render(self, context, template):
# Select different component based on `feature_new_table` kwarg
if self.kwargs.get("feature_new_table"):
comp_cls = NewTable
else:
comp_cls = OldTable
# Render the selected component
return comp_cls.render(
args=self.args,
kwargs=self.kwargs,
slots=self.slots,
context=context,
)
```
#### Post-processing rendered template
When you render the original template in [`on_render()`](../../../reference/api#django_components.Component.on_render) as:
```py
template.render(context)
```
The result is NOT the final output, but an intermediate result. Nested components
are not rendered yet.
Instead, django-components needs to take this result and process it
to actually render the child components.
To access the final output, you can `yield` the result instead of returning it.
This will return a tuple of (rendered HTML, error). The error is `None` if the rendering succeeded.
```py
class MyTable(Component):
def on_render(self, context, template):
html, error = yield template.render(context)
if error is None:
# The rendering succeeded
return html
else:
# The rendering failed
print(f"Error: {error}")
```
At this point you can do 3 things:
1. Return a new HTML
The new HTML will be used as the final output.
If the original template raised an error, it will be ignored.
```py
class MyTable(Component):
def on_render(self, context, template):
html, error = yield template.render(context)
return "NEW HTML"
```
2. Raise a new exception
The new exception is what will bubble up from the component.
The original HTML and original error will be ignored.
```py
class MyTable(Component):
def on_render(self, context, template):
html, error = yield template.render(context)
raise Exception("Error message")
```
3. Return nothing (or `None`) to handle the result as usual
If you don't raise an exception, and neither return a new HTML,
then original HTML / error will be used:
- If rendering succeeded, the original HTML will be used as the final output.
- If rendering failed, the original error will be propagated.
```py
class MyTable(Component):
def on_render(self, context, template):
html, error = yield template.render(context)
if error is not None:
# The rendering failed
print(f"Error: {error}")
```
#### Example: ErrorBoundary
[`on_render()`](../../../reference/api#django_components.Component.on_render) can be used to
implement React's [ErrorBoundary](https://react.dev/reference/react/Component#catching-rendering-errors-with-an-error-boundary).
That is, a component that catches errors in nested components and displays a fallback UI instead:
```django
{% component "error_boundary" %}
{% fill "content" %}
{% component "nested_component" %}
{% endfill %}
{% fill "fallback" %}
Sorry, something went wrong.
{% endfill %}
{% endcomponent %}
```
To implement this, we render the fallback slot in [`on_render()`](../../../reference/api#django_components.Component.on_render)
and return it if an error occured:
```djc_py
class ErrorFallback(Component):
template = """
{% slot "content" default / %}
"""
def on_render(self, context, template):
fallback = self.slots.fallback
if fallback is None:
raise ValueError("fallback slot is required")
html, error = yield template.render(context)
if error is not None:
return fallback()
else:
return html
```
### `on_render_after`
```py
def on_render_after(
self: Component,
context: Context,
template: Optional[Template],
result: Optional[str | SafeString],
error: Optional[Exception],
) -> Union[str, SafeString, None]:
```
[`on_render_after()`](../../../reference/api#django_components.Component.on_render_after) runs when the component was fully rendered,
including all its children.
It receives the same arguments as [`on_render_before()`](#on_render_before),
plus the outcome of the rendering:
- `result`: The rendered output of the component. `None` if the rendering failed.
- `error`: The error that occurred during the rendering, or `None` if the rendering succeeded.
[`on_render_after()`](../../../reference/api#django_components.Component.on_render_after) behaves the same way
as the second part of [`on_render()`](#on_render) (after the `yield`).
```py
class MyTable(Component):
def on_render_after(self, context, template, result, error):
if error is None:
# The rendering succeeded
return result
else:
# The rendering failed
print(f"Error: {error}")
```
Same as [`on_render()`](#on_render),
you can return a new HTML, raise a new exception, or return nothing:
1. Return a new HTML
The new HTML will be used as the final output.
If the original template raised an error, it will be ignored.
```py
class MyTable(Component):
def on_render_after(self, context, template, result, error):
return "NEW HTML"
```
2. Raise a new exception
The new exception is what will bubble up from the component.
The original HTML and original error will be ignored.
```py
class MyTable(Component):
def on_render_after(self, context, template, result, error):
raise Exception("Error message")
```
3. Return nothing (or `None`) to handle the result as usual
If you don't raise an exception, and neither return a new HTML,
then original HTML / error will be used:
- If rendering succeeded, the original HTML will be used as the final output.
- If rendering failed, the original error will be propagated.
```py
class MyTable(Component):
def on_render_after(self, context, template, result, error):
if error is not None:
# The rendering failed
print(f"Error: {error}")
```
## Example
You can use hooks together with [provide / inject](#how-to-use-provide--inject) to create components
that accept a list of items via a slot.

View file

@ -1,8 +1,7 @@
Django-components provides a seamless integration with HTML fragments ([HTML over the wire](https://hotwired.dev/)),
whether you're using HTMX, AlpineJS, or vanilla JavaScript.
Django-components provides a seamless integration with HTML fragments with AJAX ([HTML over the wire](https://hotwired.dev/)),
whether you're using jQuery, HTMX, AlpineJS, vanilla JavaScript, or other.
When you define a component that has extra JS or CSS, and you use django-components
to render the fragment, django-components will:
If the fragment component has any JS or CSS, django-components will:
- Automatically load the associated JS and CSS
- Ensure that JS is loaded and executed only once even if the fragment is inserted multiple times
@ -22,19 +21,23 @@ to render the fragment, django-components will:
4. A library like HTMX, AlpineJS, or custom function inserts the new HTML into
the correct place.
## Document and fragment types
## Document and fragment strategies
Components support two modes of rendering - As a "document" or as a "fragment".
Components support different ["strategies"](../../advanced/rendering_js_css#dependencies-strategies)
for rendering JS and CSS.
Two of them are used to enable HTML fragments - ["document"](../../advanced/rendering_js_css#document)
and ["fragment"](../../advanced/rendering_js_css#fragment).
What's the difference?
### Document mode
### Document strategy
Document mode assumes that the rendered components will be embedded into the HTML
Document strategy assumes that the rendered components will be embedded into the HTML
of the initial page load. This means that:
- The JS and CSS is embedded into the HTML as `<script>` and `<style>` tags
(see [JS and CSS output locations](./rendering_js_css.md#js-and-css-output-locations))
(see [Default JS / CSS locations](./rendering_js_css.md#default-js-css-locations))
- Django-components injects a JS script for managing JS and CSS assets
A component is rendered as a "document" when:
@ -42,7 +45,7 @@ A component is rendered as a "document" when:
- It is embedded inside a template as [`{% component %}`](../../reference/template_tags.md#component)
- It is rendered with [`Component.render()`](../../../reference/api#django_components.Component.render)
or [`Component.render_to_response()`](../../../reference/api#django_components.Component.render_to_response)
with the `type` kwarg set to `"document"` (default)
with the `deps_strategy` kwarg set to `"document"` (default)
Example:
@ -55,13 +58,13 @@ MyTable.render(
MyTable.render(
kwargs={...},
type="document",
deps_strategy="document",
)
```
### Fragment mode
### Fragment strategy
Fragment mode assumes that the main HTML has already been rendered and loaded on the page.
Fragment strategy assumes that the main HTML has already been rendered and loaded on the page.
The component renders HTML that will be inserted into the page as a fragment, at a LATER time:
- JS and CSS is not directly embedded to avoid duplicately executing the same JS scripts.
@ -75,14 +78,14 @@ A component is rendered as "fragment" when:
- It is rendered with [`Component.render()`](../../../reference/api#django_components.Component.render)
or [`Component.render_to_response()`](../../../reference/api#django_components.Component.render_to_response)
with the `type` kwarg set to `"fragment"`
with the `deps_strategy` kwarg set to `"fragment"`
Example:
```py
MyTable.render(
kwargs={...},
type="fragment",
deps_strategy="fragment",
)
```
@ -101,14 +104,12 @@ Then navigate to these URLs:
### 1. Define document HTML
This is the HTML into which a fragment will be loaded using HTMX.
```djc_py title="[root]/components/demo.py"
from django_components import Component, types
# HTML into which a fragment will be loaded using HTMX
class MyPage(Component):
def get(self, request):
return self.render_to_response()
template = """
{% load component_tags %}
<!DOCTYPE html>
@ -132,18 +133,20 @@ class MyPage(Component):
</body>
</html>
"""
class View:
def get(self, request):
return self.component_cls.render_to_response(request=request)
```
### 2. Define fragment HTML
The fragment to be inserted into the document.
IMPORTANT: Don't forget to set `deps_strategy="fragment"`
```djc_py title="[root]/components/demo.py"
class Frag(Component):
def get(self, request):
return self.render_to_response(
# IMPORTANT: Don't forget `type="fragment"`
type="fragment",
)
template = """
<div class="frag">
123
@ -160,6 +163,14 @@ class Frag(Component):
background: blue;
}
"""
class View:
def get(self, request):
return self.component_cls.render_to_response(
request=request,
# IMPORTANT: Don't forget `deps_strategy="fragment"`
deps_strategy="fragment",
)
```
### 3. Create view and URLs
@ -179,14 +190,12 @@ urlpatterns = [
### 1. Define document HTML
This is the HTML into which a fragment will be loaded using AlpineJS.
```djc_py title="[root]/components/demo.py"
from django_components import Component, types
# HTML into which a fragment will be loaded using AlpineJS
class MyPage(Component):
def get(self, request):
return self.render_to_response()
template = """
{% load component_tags %}
<!DOCTYPE html>
@ -216,18 +225,20 @@ class MyPage(Component):
</body>
</html>
"""
class View:
def get(self, request):
return self.component_cls.render_to_response(request=request)
```
### 2. Define fragment HTML
The fragment to be inserted into the document.
IMPORTANT: Don't forget to set `deps_strategy="fragment"`
```djc_py title="[root]/components/demo.py"
class Frag(Component):
def get(self, request):
# IMPORTANT: Don't forget `type="fragment"`
return self.render_to_response(
type="fragment",
)
# NOTE: We wrap the actual fragment in a template tag with x-if="false" to prevent it
# from being rendered until we have registered the component with AlpineJS.
template = """
@ -257,6 +268,14 @@ class Frag(Component):
background: blue;
}
"""
class View:
def get(self, request):
return self.component_cls.render_to_response(
request=request,
# IMPORTANT: Don't forget `deps_strategy="fragment"`
deps_strategy="fragment",
)
```
### 3. Create view and URLs
@ -276,14 +295,12 @@ urlpatterns = [
### 1. Define document HTML
This is the HTML into which a fragment will be loaded using vanilla JS.
```djc_py title="[root]/components/demo.py"
from django_components import Component, types
# HTML into which a fragment will be loaded using JS
class MyPage(Component):
def get(self, request):
return self.render_to_response()
template = """
{% load component_tags %}
<!DOCTYPE html>
@ -312,18 +329,20 @@ class MyPage(Component):
</body>
</html>
"""
class View:
def get(self, request):
return self.component_cls.render_to_response(request=request)
```
### 2. Define fragment HTML
The fragment to be inserted into the document.
IMPORTANT: Don't forget to set `deps_strategy="fragment"`
```djc_py title="[root]/components/demo.py"
class Frag(Component):
def get(self, request):
return self.render_to_response(
# IMPORTANT: Don't forget `type="fragment"`
type="fragment",
)
template = """
<div class="frag">
123
@ -340,6 +359,14 @@ class Frag(Component):
background: blue;
}
"""
class View:
def get(self, request):
return self.component_cls.render_to_response(
request=request,
# IMPORTANT: Don't forget `deps_strategy="fragment"`
deps_strategy="fragment",
)
```
### 3. Create view and URLs

View file

@ -1,11 +1,13 @@
_New in version 0.80_:
Django components supports the provide / inject or ContextProvider pattern with the combination of:
`django-components` supports the provide / inject pattern, similarly to React's [Context Providers](https://react.dev/learn/passing-data-deeply-with-context) or Vue's [provide / inject](https://vuejs.org/guide/components/provide-inject).
1. `{% provide %}` tag
1. `inject()` method of the `Component` class
This is achieved with the combination of:
## What is "prop drilling"?
- [`{% provide %}`](../../../reference/template_tags/#provide) tag
- [`Component.inject()`](../../../reference/api/#django_components.Component.inject) method
## What is "prop drilling"
Prop drilling refers to a scenario in UI development where you need to pass data through many layers of a component tree to reach the nested components that actually need the data.
@ -19,8 +21,6 @@ With provide / inject, a parent component acts like a data hub for all its desce
This feature is inspired by Vue's [Provide / Inject](https://vuejs.org/guide/components/provide-inject) and React's [Context / useContext](https://react.dev/learn/passing-data-deeply-with-context).
## How to use provide / inject
As the name suggest, using provide / inject consists of 2 steps
1. Providing data
@ -28,77 +28,84 @@ As the name suggest, using provide / inject consists of 2 steps
For examples of advanced uses of provide / inject, [see this discussion](https://github.com/django-components/django-components/pull/506#issuecomment-2132102584).
## Using `{% provide %}` tag
## Providing data
First we use the `{% provide %}` tag to define the data we want to "provide" (make available).
First we use the [`{% provide %}`](../../../reference/template_tags/#provide) tag to define the data we want to "provide" (make available).
```django
{% provide "my_data" key="hi" another=123 %}
{% provide "my_data" hello="hi" another=123 %}
{% component "child" / %} <--- Can access "my_data"
{% endprovide %}
{% component "child" / %} <--- Cannot access "my_data"
```
Notice that the `provide` tag REQUIRES a name as a first argument. This is the _key_ by which we can then access the data passed to this tag.
The first argument to the [`{% provide %}`](../../../reference/template_tags/#provide) tag is the _key_ by which we can later access the data passed to this tag. The key in this case is `"my_data"`.
`provide` tag name must resolve to a valid identifier (AKA a valid Python variable name).
The key must resolve to a valid identifier (AKA a valid Python variable name).
Once you've set the name, you define the data you want to "provide" by passing it as keyword arguments. This is similar to how you pass data to the `{% with %}` tag.
Next you define the data you want to "provide" by passing them as keyword arguments. This is similar to how you pass data to the [`{% with %}`](https://docs.djangoproject.com/en/5.2/ref/templates/builtins/#with) tag or the [`{% slot %}`](../../../reference/template_tags/#slot) tag.
> NOTE: Kwargs passed to `{% provide %}` are NOT added to the context.
> In the example below, the `{{ key }}` won't render anything:
>
> ```django
> {% provide "my_data" key="hi" another=123 %}
> {{ key }}
> {% endprovide %}
> ```
!!! note
Similarly to [slots and fills](#dynamic-slots-and-fills), also provide's name argument can be set dynamically via a variable, a template expression, or a spread operator:
Kwargs passed to `{% provide %}` are NOT added to the context.
In the example below, the `{{ hello }}` won't render anything:
```django
{% provide "my_data" hello="hi" another=123 %}
{{ hello }}
{% endprovide %}
```
Similarly to [slots and fills](../../fundamentals/slots/#dynamic-slots-and-fills), also provide's name argument can be set dynamically via a variable, a template expression, or a spread operator:
```django
{% provide name=name ... %}
...
{% provide %}
</table>
{% with my_name="my_name" %}
{% provide name=my_name ... %}
...
{% endprovide %}
{% endwith %}
```
## Using `inject()` method
## Injecting data
To "inject" (access) the data defined on the `provide` tag, you can use the `inject()` method inside of `get_context_data()`.
To "inject" (access) the data defined on the [`{% provide %}`](../../../reference/template_tags/#provide) tag,
you can use the [`Component.inject()`](../../../reference/api/#django_components.Component.inject) method from within any other component methods.
For a component to be able to "inject" some data, the component (`{% component %}` tag) must be nested inside the `{% provide %}` tag.
For a component to be able to "inject" some data, the component ([`{% component %}`](../../../reference/template_tags/#component) tag) must be nested inside the [`{% provide %}`](../../../reference/template_tags/#provide) tag.
In the example from previous section, we've defined two kwargs: `key="hi" another=123`. That means that if we now inject `"my_data"`, we get an object with 2 attributes - `key` and `another`.
In the example from previous section, we've defined two kwargs: `hello="hi" another=123`. That means that if we now inject `"my_data"`, we get an object with 2 attributes - `hello` and `another`.
```py
class ChildComponent(Component):
def get_context_data(self):
def get_template_data(self, args, kwargs, slots, context):
my_data = self.inject("my_data")
print(my_data.key) # hi
print(my_data.another) # 123
return {}
print(my_data.hello) # hi
print(my_data.another) # 123
```
First argument to `inject` is the _key_ (or _name_) of the provided data. This
must match the string that you used in the `provide` tag. If no provider
with given key is found, `inject` raises a `KeyError`.
First argument to [`Component.inject()`](../../../reference/api/#django_components.Component.inject) is the _key_ (or _name_) of the provided data. This
must match the string that you used in the [`{% provide %}`](../../../reference/template_tags/#provide) tag.
To avoid the error, you can pass a second argument to `inject` to which will act as a default value, similar to `dict.get(key, default)`:
If no provider with given key is found, [`inject()`](../../../reference/api/#django_components.Component.inject) raises a `KeyError`.
To avoid the error, you can pass a second argument to [`inject()`](../../../reference/api/#django_components.Component.inject). This will act as a default value similar to `dict.get(key, default)`:
```py
class ChildComponent(Component):
def get_context_data(self):
def get_template_data(self, args, kwargs, slots, context):
my_data = self.inject("invalid_key", DEFAULT_DATA)
assert my_data == DEFAUKT_DATA
return {}
assert my_data == DEFAULT_DATA
```
The instance returned from `inject()` is a subclass of `NamedTuple`, so the instance is immutable. This ensures that the data returned from `inject` will always
have all the keys that were passed to the `provide` tag.
!!! note
> NOTE: `inject()` works strictly only in `get_context_data`. If you try to call it from elsewhere, it will raise an error.
The instance returned from [`inject()`](../../../reference/api/#django_components.Component.inject) is immutable (subclass of [`NamedTuple`](https://docs.python.org/3/library/typing.html#typing.NamedTuple)). This ensures that the data returned from [`inject()`](../../../reference/api/#django_components.Component.inject) will always
have all the keys that were passed to the [`{% provide %}`](../../../reference/template_tags/#provide) tag.
!!! warning
[`inject()`](../../../reference/api/#django_components.Component.inject) works strictly only during render execution. If you try to call `inject()` from outside, it will raise an error.
## Full example
@ -106,17 +113,17 @@ have all the keys that were passed to the `provide` tag.
@register("child")
class ChildComponent(Component):
template = """
<div> {{ my_data.key }} </div>
<div> {{ my_data.hello }} </div>
<div> {{ my_data.another }} </div>
"""
def get_context_data(self):
def get_template_data(self, args, kwargs, slots, context):
my_data = self.inject("my_data", "default")
return {"my_data": my_data}
template_str = """
{% load component_tags %}
{% provide "my_data" key="hi" another=123 %}
{% provide "my_data" hello="hi" another=123 %}
{% component "child" / %}
{% endprovide %}
"""

View file

@ -1,15 +1,20 @@
### JS and CSS output locations
## Introduction
If:
Components consist of 3 parts - HTML, JS and CSS.
1. Your components use JS and CSS via any of:
- [`Component.css`](#TODO)
- [`Component.js`](#TODO)
- [`Component.Media.css`](#TODO)
- [`Component.Media.js`](#TODO)
2. And you use the [`ComponentDependencyMiddleware`](#TODO) middleware
Handling of HTML is straightforward - it is rendered as is, and inserted where
the [`{% component %}`](../../../reference/template_tags#component) tag is.
Then, by default, the components' JS and CSS will be automatically inserted into the HTML:
However, handling of JS and CSS is more complex:
- JS and CSS is are inserted elsewhere in the HTML. As a best practice, JS is placed in the `<body>` HTML tag, and CSS in the `<head>`.
- Multiple components may use the same JS and CSS files. We don't want to load the same files multiple times.
- Fetching of JS and CSS may block the page, so the JS / CSS should be embedded in the HTML.
- Components inserted as HTML fragments need different handling for JS and CSS.
## Default JS / CSS locations
If your components use JS and CSS then, by default, the JS and CSS will be automatically inserted into the HTML:
- CSS styles will be inserted at the end of the `<head>`
- JS scripts will be inserted at the end of the `<body>`
@ -17,8 +22,8 @@ Then, by default, the components' JS and CSS will be automatically inserted into
If you want to place the dependencies elsewhere in the HTML, you can override
the locations by inserting following Django template tags:
- [`{% component_js_dependencies %}`](#TODO) - Set new location(s) for JS scripts
- [`{% component_css_dependencies %}`](#TODO) - Set new location(s) for CSS styles
- [`{% component_js_dependencies %}`](../../../reference/template_tags#component_js_dependencies) - Set new location(s) for JS scripts
- [`{% component_css_dependencies %}`](../../../reference/template_tags#component_css_dependencies) - Set new location(s) for CSS styles
So if you have a component with JS and CSS:
@ -31,6 +36,7 @@ class MyButton(Component):
Click me!
</button>
"""
js: types.js = """
for (const btnEl of document.querySelectorAll(".my-button")) {
btnEl.addEventListener("click", () => {
@ -38,6 +44,7 @@ class MyButton(Component):
});
}
"""
css: types.css """
.my-button {
background: green;
@ -49,11 +56,13 @@ class MyButton(Component):
css = ["/extra/style.css"]
```
Then the JS from `MyButton.js` and `MyButton.Media.js` will be rendered at the default place,
or in [`{% component_js_dependencies %}`](#TODO).
Then:
And the CSS from `MyButton.css` and `MyButton.Media.css` will be rendered at the default place,
or in [`{% component_css_dependencies %}`](#TODO).
- JS from `MyButton.js` and `MyButton.Media.js` will be rendered at the default place (`<body>`),
or in [`{% component_js_dependencies %}`](../../../reference/template_tags#component_js_dependencies).
- CSS from `MyButton.css` and `MyButton.Media.css` will be rendered at the default place (`<head>`),
or in [`{% component_css_dependencies %}`](../../../reference/template_tags#component_css_dependencies).
And if you don't specify `{% component_dependencies %}` tags, it is the equivalent of:
@ -74,52 +83,307 @@ And if you don't specify `{% component_dependencies %}` tags, it is the equivale
</html>
```
### Setting up the middleware
!!! warning
[`ComponentDependencyMiddleware`](#TODO) is a Django [middleware](https://docs.djangoproject.com/en/5.1/topics/http/middleware/)
designed to manage and inject CSS / JS dependencies of rendered components dynamically.
It ensures that only the necessary stylesheets and scripts are loaded
in your HTML responses, based on the components used in your Django templates.
If the rendered HTML does NOT contain neither `{% component_dependencies %}` template tags,
nor `<head>` and `<body>` HTML tags, then the JS and CSS will NOT be inserted!
To set it up, add the middleware to your [`MIDDLEWARE`](https://docs.djangoproject.com/en/5.1/ref/settings/#std-setting-MIDDLEWARE)
in `settings.py`:
To force the JS and CSS to be inserted, use the [`"append"`](#append) or [`"prepend"`](#prepend)
strategies.
## Dependencies strategies
The rendered HTML may be used in different contexts (browser, email, etc).
If your components use JS and CSS scripts, you may need to handle them differently.
The different ways for handling JS / CSS are called **"dependencies strategies"**.
[`render()`](../../../reference/api/#django_components.Component.render) and [`render_to_response()`](../../../reference/api/#django_components.Component.render_to_response)
accept a `deps_strategy` parameter, which controls where and how the JS / CSS are inserted into the HTML.
```python
MIDDLEWARE = [
# ... other middleware classes ...
'django_components.middleware.ComponentDependencyMiddleware'
# ... other middleware classes ...
]
main_page = MainPage.render(deps_strategy="document")
fragment = MyComponent.render_to_response(deps_strategy="fragment")
```
### `render_dependencies` and rendering JS / CSS without the middleware
The `deps_strategy` parameter is set at the root of a component render tree, which is why it is not available for
the [`{% component %}`](../../../reference/template_tags#component) tag.
For most scenarios, using the [`ComponentDependencyMiddleware`](#TODO) middleware will be just fine.
When you use Django's [`django.shortcuts.render()`](https://docs.djangoproject.com/en/5.2/topics/http/shortcuts/#render)
or [`Template.render()`](https://docs.djangoproject.com/en/5.2/ref/templates/api/#django.template.Template.render) to render templates,
you can't directly set the `deps_strategy` parameter.
However, this section is for you if you want to:
In this case, you can set the `deps_strategy` with the `DJC_DEPS_STRATEGY` context variable.
- Render HTML that will NOT be sent as a server response
- Insert pre-rendered HTML into another component
- Render HTML fragments (partials)
```python
from django.template.context import Context
from django.shortcuts import render
Every time there is an HTML string that has parts which were rendered using components,
and any of those components has JS / CSS, then this HTML string MUST be processed with [`render_dependencies()`](#TODO).
ctx = Context({"DJC_DEPS_STRATEGY": "fragment"})
fragment = render(request, "my_component.html", ctx=ctx)
```
It is actually [`render_dependencies()`](#TODO) that finds all used components in the HTML string,
and inserts the component's JS and CSS into `{% component_dependencies %}` tags, or at the default locations.
!!! info
#### Render JS / CSS without the middleware
The `deps_strategy` parameter is ultimately passed to [`render_dependencies()`](../../../reference/api/#django_components.render_dependencies).
The truth is that the [`ComponentDependencyMiddleware`](#TODO) middleware just calls [`render_dependencies()`](#TODO),
passing in the HTML content. So if you render a template that contained [`{% component %}`](#TODO) tags,
you MUST pass the result through [`render_dependencies()`](#TODO). And the middleware is just one of the options.
!!! note "Why is `deps_strategy` required?"
Here is how you can achieve the same, without the middleware, using [`render_dependencies()`](#TODO):
This is a technical limitation of the current implementation.
When a component is rendered, django-components embeds metadata about the component's JS and CSS into the HTML.
This way we can compose components together, and know which JS / CSS dependencies are needed.
As the last step of rendering, django-components extracts this metadata and uses a selected strategy
to insert the JS / CSS into the HTML.
There are six dependencies strategies:
- [`document`](../../advanced/rendering_js_css#document) (default)
- Smartly inserts JS / CSS into placeholders or into `<head>` and `<body>` tags.
- Inserts extra script to allow `fragment` strategy to work.
- Assumes the HTML will be rendered in a JS-enabled browser.
- [`fragment`](../../advanced/rendering_js_css#fragment)
- A lightweight HTML fragment to be inserted into a document with AJAX.
- No JS / CSS included.
- [`simple`](../../advanced/rendering_js_css#simple)
- Smartly insert JS / CSS into placeholders or into `<head>` and `<body>` tags.
- No extra script loaded.
- [`prepend`](../../advanced/rendering_js_css#prepend)
- Insert JS / CSS before the rendered HTML.
- No extra script loaded.
- [`append`](../../advanced/rendering_js_css#append)
- Insert JS / CSS after the rendered HTML.
- No extra script loaded.
- [`ignore`](../../advanced/rendering_js_css#ignore)
- HTML is left as-is. You can still process it with a different strategy later with
[`render_dependencies()`](../../../reference/api/#django_components.render_dependencies).
- Used for inserting rendered HTML into other components.
### `document`
`deps_strategy="document"` is the default. Use this if you are rendering a whole page, or if no other option suits better.
```python
html = Button.render(deps_strategy="document")
```
When you render a component tree with the `"document"` strategy, it is expected that:
- The HTML will be rendered at page load.
- The HTML will be inserted into a page / browser where JS can be executed.
**Location:**
JS and CSS is inserted:
- Preferentially into JS / CSS placeholders like [`{% component_js_dependencies %}`](../../../reference/template_tags#component_js_dependencies)
- Otherwise, JS into `<body>` element, and CSS into `<head>` element
- If neither found, JS / CSS are NOT inserted
**Included scripts:**
For the `"document"` strategy, the JS and CSS is set up to avoid any delays when the end user loads
the page in the browser:
- Components' primary JS and CSS scripts ([`Component.js`](../../../reference/api/#django_components.Component.js)
and [`Component.css`](../../../reference/api/#django_components.Component.css)) - fully inlined:
```html
<script>
console.log("Hello from Button!");
</script>
<style>
.button {
background-color: blue;
}
</style>
```
- Components' secondary JS and CSS scripts
([`Component.Media`](../../../reference/api/#django_components.Component.Media)) - inserted as links:
```html
<link rel="stylesheet" href="https://example.com/styles.css" />
<script src="https://example.com/script.js"></script>
```
- A JS script is injected to manage component dependencies, enabling lazy loading of JS and CSS
for HTML fragments.
!!! info
This strategy is required for fragments to work properly, as it sets up the dependency manager that fragments rely on.
!!! note "How the dependency manager works"
The dependency manager is a JS script that keeps track of all the JS and CSS dependencies that have already been loaded.
When a fragment is inserted into the page, it will also insert a JSON `<script>` tag with fragment metadata.
The dependency manager will pick up on that, and check which scripts the fragment needs.
It will then fetch only the scripts that haven't been loaded yet.
### `fragment`
`deps_strategy="fragment"` is used when rendering a piece of HTML that will be inserted into a page
that has already been rendered with the [`"document"`](#document) strategy:
```python
fragment = MyComponent.render(deps_strategy="fragment")
```
The HTML of fragments is very lightweight because it doesn't include the JS and CSS scripts
of the rendered components.
With fragments, even if a component has JS and CSS, you can insert the same component into a page
hundreds of times, and the JS and CSS will only ever be loaded once.
This is intended for dynamic content that's loaded with AJAX after the initial page load, such as with [jQuery](https://jquery.com/), [HTMX](https://htmx.org/), [AlpineJS](https://alpinejs.dev/) or similar libraries.
**Location:**
None. The fragment's JS and CSS files will be loaded dynamically into the page.
**Included scripts:**
- A special JSON `<script>` tag that tells the dependency manager what JS and CSS to load.
### `simple`
`deps_strategy="simple"` is used either for non-browser use cases, or when you don't want to use the dependency manager.
Practically, this is the same as the [`"document"`](#document) strategy, except that the dependency manager is not used.
```python
html = MyComponent.render(deps_strategy="simple")
```
**Location:**
JS and CSS is inserted:
- Preferentially into JS / CSS placeholders like [`{% component_js_dependencies %}`](../../../reference/template_tags#component_js_dependencies)
- Otherwise, JS into `<body>` element, and CSS into `<head>` element
- If neither found, JS / CSS are NOT inserted
**Included scripts:**
- Components' primary JS and CSS scripts ([`Component.js`](../../../reference/api/#django_components.Component.js)
and [`Component.css`](../../../reference/api/#django_components.Component.css)) - fully inlined:
```html
<script>
console.log("Hello from Button!");
</script>
<style>
.button {
background-color: blue;
}
</style>
```
- Components' secondary JS and CSS scripts
([`Component.Media`](../../../reference/api/#django_components.Component.Media)) - inserted as links:
```html
<link rel="stylesheet" href="https://example.com/styles.css" />
<script src="https://example.com/script.js"></script>
```
- No extra scripts are inserted.
### `prepend`
This is the same as [`"simple"`](#simple), but placeholders like [`{% component_js_dependencies %}`](../../../reference/template_tags#component_js_dependencies) and HTML tags `<head>` and `<body>` are all ignored. The JS and CSS are **always** inserted **before** the rendered content.
```python
html = MyComponent.render(deps_strategy="prepend")
```
**Location:**
JS and CSS is **always** inserted before the rendered content.
**Included scripts:**
Same as for the [`"simple"`](#simple) strategy.
### `append`
This is the same as [`"simple"`](#simple), but placeholders like [`{% component_js_dependencies %}`](../../../reference/template_tags#component_js_dependencies) and HTML tags `<head>` and `<body>` are all ignored. The JS and CSS are **always** inserted **after** the rendered content.
```python
html = MyComponent.render(deps_strategy="append")
```
**Location:**
JS and CSS is **always** inserted after the rendered content.
**Included scripts:**
Same as for the [`"simple"`](#simple) strategy.
### `ignore`
`deps_strategy="ignore"` is used when you do NOT want to process JS and CSS of the rendered HTML.
```python
html = MyComponent.render(deps_strategy="ignore")
```
The rendered HTML is left as-is. You can still process it with a different strategy later with `render_dependencies()`.
This is useful when you want to insert rendered HTML into another component.
```python
html = MyComponent.render(deps_strategy="ignore")
html = AnotherComponent.render(slots={"content": html})
```
## Manually rendering JS / CSS
When rendering templates or components, django-components covers all the traditional ways how components
or templates can be rendered:
- [`Component.render()`](../../../reference/api/#django_components.Component.render)
- [`Component.render_to_response()`](../../../reference/api/#django_components.Component.render_to_response)
- [`Template.render()`](https://docs.djangoproject.com/en/5.2/ref/templates/api/#django.template.Template.render)
- [`django.shortcuts.render()`](https://docs.djangoproject.com/en/5.2/topics/http/shortcuts/#render)
This way you don't need to manually handle rendering of JS / CSS.
However, for advanced or low-level use cases, you may need to control when to render JS / CSS.
In such case you can directly pass rendered HTML to [`render_dependencies()`](../../../reference/api/#django_components.render_dependencies).
This function will extract all used components in the HTML string, and insert the components' JS and CSS
based on given strategy.
!!! info
The truth is that all the methods listed above call [`render_dependencies()`](../../../reference/api/#django_components.render_dependencies)
internally.
**Example:**
To see how [`render_dependencies()`](../../../reference/api/#django_components.render_dependencies) works,
let's render a template with a component.
We will render it twice:
- First time, we let `template.render()` handle the rendering.
- Second time, we prevent `template.render()` from inserting the component's JS and CSS with `deps_strategy="ignore"`.
Instead, we pass the "unprocessed" HTML to `render_dependencies()` ourselves to insert the component's JS and CSS.
```python
from django.template.base import Template
from django.template.context import Context
from django_component import render_dependencies
from django_components import render_dependencies
template = Template("""
{% load component_tags %}
@ -138,85 +402,30 @@ template = Template("""
</html>
""")
rendered = template.render(Context())
rendered = render_dependencies(rendered)
rendered = template.render(Context({}))
rendered2_raw = template.render(Context({"DJC_DEPS_STRATEGY": "ignore"}))
rendered2 = render_dependencies(rendered2_raw)
assert rendered == rendered2
```
Same applies if you render a template using Django's [`django.shortcuts.render`](https://docs.djangoproject.com/en/5.1/topics/http/shortcuts/#render):
Same applies to other strategies and other methods of rendering:
```python
from django.shortcuts import render
raw_html = MyComponent.render(deps_strategy="ignore")
html = render_dependencies(raw_html, deps_strategy="document")
def my_view(request):
rendered = render(request, "pages/home.html")
rendered = render_dependencies(rendered)
return rendered
html2 = MyComponent.render(deps_strategy="document")
assert html == html2
```
Alternatively, when you render HTML with [`Component.render()`](#TODO)
or [`Component.render_to_response()`](#TODO),
these, by default, call [`render_dependencies()`](#TODO) for you, so you don't have to:
## HTML fragments
```python
from django_components import Component
Django-components provides a seamless integration with HTML fragments with AJAX ([HTML over the wire](https://hotwired.dev/)),
whether you're using jQuery, HTMX, AlpineJS, vanilla JavaScript, or other.
class MyButton(Component):
...
This is achieved by the combination of the [`"document"`](#document) and [`"fragment"`](#fragment) strategies.
# No need to call `render_dependencies()`
rendered = MyButton.render()
```
#### Inserting pre-rendered HTML into another component
In previous section we've shown that [`render_dependencies()`](#TODO) does NOT need to be called
when you render a component via [`Component.render()`](#TODO).
API of django_components makes it possible to compose components in a "React-like" way,
where we pre-render a piece of HTML and then insert it into a larger structure.
To do this, you must add [`render_dependencies=False`](#TODO) to the nested components:
```python
card_actions = CardActions.render(
kwargs={"editable": editable},
render_dependencies=False,
)
card = Card.render(
slots={"actions": card_actions},
render_dependencies=False,
)
page = MyPage.render(
slots={"card": card},
)
```
Why is `render_dependencies=False` required?
This is a technical limitation of the current implementation.
As mentioned earlier, each time we call [`Component.render()`](#TODO),
we also call [`render_dependencies()`](#TODO).
However, there is a problem here - When we call [`render_dependencies()`](#TODO)
inside [`CardActions.render()`](#TODO),
we extract and REMOVE the info on components' JS and CSS from the HTML. But the template
of `CardActions` contains no `{% component_depedencies %}` tags, and nor `<head>` nor `<body>` HTML tags.
So the component's JS and CSS will NOT be inserted, and will be lost.
To work around this, you must set [`render_dependencies=False`](#TODO) when rendering pieces of HTML
with [`Component.render()`](#TODO) and inserting them into larger structures.
#### Summary
1. Every time you render HTML that contained components, you have to call [`render_dependencies()`](#TODO)
on the rendered output.
2. There are several ways to call [`render_dependencies()`](#TODO):
- Using the [`ComponentDependencyMiddleware`](#TODO) middleware
- Rendering the HTML by calling [`Component.render()`](#TODO) with `render_dependencies=True` (default)
- Rendering the HTML by calling [`Component.render_to_response()`](#TODO) (always renders dependencies)
- Directly passing rendered HTML to [`render_dependencies()`](#TODO)
3. If you pre-render one component to pass it into another, the pre-rendered component must be rendered with
[`render_dependencies=False`](#TODO).
Read more about [HTML fragments](../../advanced/html_fragments).

View file

@ -52,7 +52,7 @@ This will allow you to use the tag in your templates like this:
### Parameters
The `@template_tag` decorator accepts the following parameters:
The [`@template_tag`](../../../reference/api#django_components.template_tag) decorator accepts the following parameters:
- `library`: The Django template library to register the tag with
- `tag`: The name of the template tag (e.g. `"mytag"` for `{% mytag %}`)
@ -61,7 +61,8 @@ The `@template_tag` decorator accepts the following parameters:
### Function signature
The function decorated with `@template_tag` must accept at least two arguments:
The function decorated with [`@template_tag`](../../../reference/api#django_components.template_tag)
must accept at least two arguments:
1. `node`: The node instance (we'll explain this in detail in the next section)
2. `context`: The Django template context
@ -150,15 +151,18 @@ GreetNode.register(library)
### Node properties
When using `BaseNode`, you have access to several useful properties:
When using [`BaseNode`](../../../reference/api#django_components.BaseNode), you have access to several useful properties:
- `node_id`: A unique identifier for this node instance
- `flags`: Dictionary of flag values (e.g. `{"required": True}`)
- `params`: List of raw parameters passed to the tag
- `nodelist`: The template nodes between the start and end tags
- `active_flags`: List of flags that are currently set to True
- [`node_id`](../../../reference/api#django_components.BaseNode.node_id): A unique identifier for this node instance
- [`flags`](../../../reference/api#django_components.BaseNode.flags): Dictionary of flag values (e.g. `{"required": True}`)
- [`params`](../../../reference/api#django_components.BaseNode.params): List of raw parameters passed to the tag
- [`nodelist`](../../../reference/api#django_components.BaseNode.nodelist): The template nodes between the start and end tags
- [`contents`](../../../reference/api#django_components.BaseNode.contents): The raw contents between the start and end tags
- [`active_flags`](../../../reference/api#django_components.BaseNode.active_flags): List of flags that are currently set to True
- [`template_name`](../../../reference/api#django_components.BaseNode.template_name): The name of the `Template` instance inside which the node was defined
- [`template_component`](../../../reference/api#django_components.BaseNode.template_component): The component class that the `Template` belongs to
This is what the `node` parameter in the `@template_tag` decorator gives you access to - it's the instance of the node class that was automatically created for your template tag.
This is what the `node` parameter in the [`@template_tag`](../../../reference/api#django_components.template_tag) decorator gives you access to - it's the instance of the node class that was automatically created for your template tag.
### Rendering content between tags

View file

@ -4,7 +4,10 @@ The [`@djc_test`](../../../reference/testing_api#djc_test) decorator is a powerf
## Usage
The [`@djc_test`](../../../reference/testing_api#djc_test) decorator can be applied to functions, methods, or classes. When applied to a class, it recursively decorates all methods starting with `test_`, including those in nested classes. This allows for comprehensive testing of component behavior.
The [`@djc_test`](../../../reference/testing_api#djc_test) decorator can be applied to functions, methods, or classes.
When applied to a class, it decorates all methods starting with `test_`, and all nested classes starting with `Test`,
recursively.
### Applying to a Function
@ -15,8 +18,6 @@ simply decorate the function as shown below:
import django
from django_components.testing import djc_test
django.setup()
@djc_test
def test_my_component():
@register("my_component")
@ -27,38 +28,34 @@ def test_my_component():
### Applying to a Class
When applied to a class, `djc_test` decorates each `test_` method individually:
When applied to a class, `djc_test` decorates each `test_` method, as well as all nested classes starting with `Test`.
```python
import django
from django_components.testing import djc_test
django.setup()
@djc_test
class TestMyComponent:
def test_something(self):
...
class Nested:
class TestNested:
def test_something_else(self):
...
```
This is equivalent to applying the decorator to each method individually:
This is equivalent to applying the decorator to both of the methods individually:
```python
import django
from django_components.testing import djc_test
django.setup()
class TestMyComponent:
@djc_test
def test_something(self):
...
class Nested:
class TestNested:
@djc_test
def test_something_else(self):
...
@ -70,19 +67,26 @@ See the API reference for [`@djc_test`](../../../reference/testing_api#djc_test)
### Setting Up Django
Before using [`djc_test`](../../../reference/testing_api#djc_test), ensure Django is set up:
If you want to define a common Django settings that would be the baseline for all tests,
you can call [`django.setup()`](https://docs.djangoproject.com/en/5.2/ref/applications/#django.setup)
before the `@djc_test` decorator:
```python
import django
from django_components.testing import djc_test
django.setup()
django.setup(...)
@djc_test
def test_my_component():
...
```
!!! info
If you omit [`django.setup()`](https://docs.djangoproject.com/en/5.2/ref/applications/#django.setup)
in the example above, `@djc_test` will call it for you, so you don't need to do it manually.
## Example: Parametrizing Context Behavior
You can parametrize the [context behavior](../../../reference/settings#django_components.app_settings.ComponentsSettings.context_behavior) using [`djc_test`](../../../reference/testing_api#djc_test):
@ -106,6 +110,6 @@ from django_components.testing import djc_test
)
)
def test_context_behavior(components_settings):
rendered = MyComponent().render()
rendered = MyComponent.render()
...
```

View file

@ -1,173 +0,0 @@
## Adding type hints with Generics
_New in version 0.92_
The `Component` class optionally accepts type parameters
that allow you to specify the types of args, kwargs, slots, and
data:
```py
class Button(Component[Args, Kwargs, Slots, Data, JsData, CssData]):
...
```
- `Args` - Must be a `Tuple` or `Any`
- `Kwargs` - Must be a `TypedDict` or `Any`
- `Data` - Must be a `TypedDict` or `Any`
- `Slots` - Must be a `TypedDict` or `Any`
Here's a full example:
```py
from typing import NotRequired, Tuple, TypedDict, SlotContent, SlotFunc
# Positional inputs
Args = Tuple[int, str]
# Kwargs inputs
class Kwargs(TypedDict):
variable: str
another: int
maybe_var: NotRequired[int] # May be ommited
# Data returned from `get_context_data`
class Data(TypedDict):
variable: str
# The data available to the `my_slot` scoped slot
class MySlotData(TypedDict):
value: int
# Slots
class Slots(TypedDict):
# Use SlotFunc for slot functions.
# The generic specifies the `data` dictionary
my_slot: NotRequired[SlotFunc[MySlotData]]
# SlotContent == Union[str, SafeString]
another_slot: SlotContent
class Button(Component[Args, Kwargs, Slots, Data, JsData, CssData]):
def get_context_data(self, variable, another):
return {
"variable": variable,
}
```
When you then call `Component.render` or `Component.render_to_response`, you will get type hints:
```py
Button.render(
# Error: First arg must be `int`, got `float`
args=(1.25, "abc"),
# Error: Key "another" is missing
kwargs={
"variable": "text",
},
)
```
### Usage for Python <3.11
On Python 3.8-3.10, use `typing_extensions`
```py
from typing_extensions import TypedDict, NotRequired
```
Additionally on Python 3.8-3.9, also import `annotations`:
```py
from __future__ import annotations
```
Moreover, on 3.10 and less, you may not be able to use `NotRequired`, and instead you will need to mark either all keys are required, or all keys as optional, using TypeDict's `total` kwarg.
[See PEP-655](https://peps.python.org/pep-0655) for more info.
## Passing additional args or kwargs
You may have a function that supports any number of args or kwargs:
```py
def get_context_data(self, *args, **kwargs):
...
```
This is not supported with the typed components.
As a workaround:
- For `*args`, set a positional argument that accepts a list of values:
```py
# Tuple of one member of list of strings
Args = Tuple[List[str]]
```
- For `*kwargs`, set a keyword argument that accepts a dictionary of values:
```py
class Kwargs(TypedDict):
variable: str
another: int
# Pass any extra keys under `extra`
extra: Dict[str, any]
```
## Handling no args or no kwargs
To declare that a component accepts no Args, Kwargs, etc, you can use `EmptyTuple` and `EmptyDict` types:
```py
from django_components import Component, EmptyDict, EmptyTuple
Args = EmptyTuple
Kwargs = Data = Slots = EmptyDict
class Button(Component[Args, Kwargs, Slots, Data, JsData, CssData]):
...
```
## Runtime input validation with types
_New in version 0.96_
> NOTE: Kwargs, slots, and data validation is supported only for Python >=3.11
In Python 3.11 and later, when you specify the component types, you will get also runtime validation of the inputs you pass to `Component.render` or `Component.render_to_response`.
So, using the example from before, if you ignored the type errors and still ran the following code:
```py
Button.render(
# Error: First arg must be `int`, got `float`
args=(1.25, "abc"),
# Error: Key "another" is missing
kwargs={
"variable": "text",
},
)
```
This would raise a `TypeError`:
```txt
Component 'Button' expected positional argument at index 0 to be <class 'int'>, got 1.25 of type <class 'float'>
```
In case you need to skip these errors, you can either set the faulty member to `Any`, e.g.:
```py
# Changed `int` to `Any`
Args = Tuple[Any, str]
```
Or you can replace `Args` with `Any` altogether, to skip the validation of args:
```py
# Replaced `Args` with `Any`
class Button(Component[Any, Kwargs, Slots, Data, JsData, CssData]):
...
```
Same applies to kwargs, data, and slots.

View file

@ -1,15 +1,17 @@
# `.nav.yml` is provided by https://lukasgeiter.github.io/mkdocs-awesome-nav
nav:
- Single-file components: single_file_components.md
- Components in Python: components_in_python.md
- Accessing component inputs: access_component_input.md
- HTML / JS / CSS files: html_js_css_files.md
- HTML / JS / CSS variables: html_js_css_variables.md
- Secondary JS / CSS files: secondary_js_css_files.md
- Component defaults: component_defaults.md
- Component context and scope: component_context_scope.md
- Template tag syntax: template_tag_syntax.md
- Render API: render_api.md
- Rendering components: rendering_components.md
- Slots: slots.md
- Template tag syntax: template_tag_syntax.md
- HTML attributes: html_attributes.md
- Defining HTML / JS / CSS files: defining_js_css_html_files.md
- Autodiscovery: autodiscovery.md
- Components as views: components_as_views.md
- Component views and URLs: component_views_urls.md
- HTTP Request: http_request.md
- Typing and validation: typing_and_validation.md
- Subclassing components: subclassing_components.md
- Autodiscovery: autodiscovery.md

View file

@ -1,34 +0,0 @@
When you call `Component.render` or `Component.render_to_response`, the inputs to these methods can be accessed from within the instance under `self.input`.
This means that you can use `self.input` inside:
- `get_context_data`
- `get_template_name`
- `get_template`
- `on_render_before`
- `on_render_after`
`self.input` is only defined during the execution of `Component.render`, and raises a `RuntimeError` when called outside of this context.
`self.input` has the same fields as the input to `Component.render`:
```python
class TestComponent(Component):
def get_context_data(self, var1, var2, variable, another, **attrs):
assert self.input.args == (123, "str")
assert self.input.kwargs == {"variable": "test", "another": 1}
assert self.input.slots == {"my_slot": "MY_SLOT"}
assert isinstance(self.input.context, Context)
return {
"variable": variable,
}
rendered = TestComponent.render(
kwargs={"variable": "test", "another": 1},
args=(123, "str"),
slots={"my_slot": "MY_SLOT"},
)
```
NOTE: The slots in `self.input.slots` are normalized to slot functions.

View file

@ -1,6 +1,15 @@
Every component that you want to use in the template with the [`{% component %}`](django_components.templateags.component_tags)
tag needs to be registered with the [`ComponentRegistry`](django_components.component_registry.ComponentRegistry).
Normally, we use the [`@register`](django_components.component_registry.register) decorator for that:
django-components automatically searches for files containing components in the
[`COMPONENTS.dirs`](../../../reference/settings#django_components.app_settings.ComponentsSettings.dirs) and
[`COMPONENTS.app_dirs`](../../../reference/settings#django_components.app_settings.ComponentsSettings.app_dirs)
directories.
### Manually register components
Every component that you want to use in the template with the
[`{% component %}`](../../../reference/template_tags#component)
tag needs to be registered with the [`ComponentRegistry`](../../../reference/api#django_components.ComponentRegistry).
We use the [`@register`](../../../reference/api#django_components.register) decorator for that:
```python
from django_components import Component, register
@ -12,6 +21,8 @@ class Calendar(Component):
But for the component to be registered, the code needs to be executed - and for that, the file needs to be imported as a module.
This is the "discovery" part of the process.
One way to do that is by importing all your components in `apps.py`:
```python
@ -30,23 +41,28 @@ class MyAppConfig(AppConfig):
However, there's a simpler way!
By default, the Python files in the [`COMPONENTS.dirs`](django_components.app_settings.ComponentsSettings.dirs) directories (and app-level [`[app]/components/`](django_components.app_settings.ComponentsSettings.app_dirs)) are auto-imported in order to auto-register the components.
### Autodiscovery
Autodiscovery occurs when Django is loaded, during the [`AppConfig.ready`](https://docs.djangoproject.com/en/5.1/ref/applications/#django.apps.AppConfig.ready)
By default, the Python files found in the
[`COMPONENTS.dirs`](../../../reference/settings#django_components.app_settings.ComponentsSettings.dirs) and
[`COMPONENTS.app_dirs`](../../../reference/settings#django_components.app_settings.ComponentsSettings.app_dirs)
are auto-imported in order to execute the code that registers the components.
Autodiscovery occurs when Django is loaded, during the [`AppConfig.ready()`](https://docs.djangoproject.com/en/5.2/ref/applications/#django.apps.AppConfig.ready)
hook of the `apps.py` file.
If you are using autodiscovery, keep a few points in mind:
- Avoid defining any logic on the module-level inside the `components` dir, that you would not want to run anyway.
- Components inside the auto-imported files still need to be registered with [`@register`](django_components.component_registry.register)
- Avoid defining any logic on the module-level inside the components directories, that you would not want to run anyway.
- Components inside the auto-imported files still need to be registered with [`@register`](../../../reference/api#django_components.register)
- Auto-imported component files must be valid Python modules, they must use suffix `.py`, and module name should follow [PEP-8](https://peps.python.org/pep-0008/#package-and-module-names).
- Subdirectories and files starting with an underscore `_` (except `__init__.py`) are ignored.
Autodiscovery can be disabled in the [settings](django_components.app_settings.ComponentsSettings.autodiscovery).
Autodiscovery can be disabled in the settings with [`autodiscover=False`](../../../reference/settings#django_components.app_settings.ComponentsSettings.autodiscover).
### Manually trigger autodiscovery
Autodiscovery can be also triggered manually, using the [`autodiscover`](django_components.autodiscovery.autodiscover) function. This is useful if you want to run autodiscovery at a custom point of the lifecycle:
Autodiscovery can be also triggered manually, using the [`autodiscover()`](../../../reference/api#django_components.autodiscover) function. This is useful if you want to run autodiscovery at a custom point of the lifecycle:
```python
from django_components import autodiscover

View file

@ -1,5 +1,5 @@
When a component is being rendered, the component inputs are passed to various methods like
[`get_context_data()`](../../../reference/api#django_components.Component.get_context_data),
[`get_template_data()`](../../../reference/api#django_components.Component.get_template_data),
[`get_js_data()`](../../../reference/api#django_components.Component.get_js_data),
or [`get_css_data()`](../../../reference/api#django_components.Component.get_css_data).
@ -10,8 +10,8 @@ no value is provided, or when the value is set to `None` for a particular input.
### Defining defaults
To define defaults for a component, you create a nested `Defaults` class within your
[`Component`](../../../reference/api#django_components.Component) class.
To define defaults for a component, you create a nested [`Defaults`](../../../reference/api#django_components.Component.Defaults)
class within your [`Component`](../../../reference/api#django_components.Component) class.
Each attribute in the `Defaults` class represents a default value for a corresponding input.
```py
@ -24,16 +24,16 @@ class MyTable(Component):
position = "left"
selected_items = Default(lambda: [1, 2, 3])
def get_context_data(self, position, selected_items):
def get_template_data(self, args, kwargs, slots, context):
return {
"position": position,
"selected_items": selected_items,
"position": kwargs["position"],
"selected_items": kwargs["selected_items"],
}
...
```
In this example, `position` is a simple default value, while `selected_items` uses a factory function wrapped in `Default` to ensure a new list is created each time the default is used.
In this example, `position` is a simple default value, while `selected_items` uses a factory function wrapped in [`Default`](../../../reference/api#django_components.Default) to ensure a new list is created each time the default is used.
Now, when we render the component, the defaults will be applied:
@ -65,6 +65,26 @@ and so `selected_items` will be set to `[1, 2, 3]`.
The defaults are aplied only to keyword arguments. They are NOT applied to positional arguments!
!!! warning
When [typing](../fundamentals/typing_and_validation.md) your components with [`Args`](../../../reference/api/#django_components.Component.Args),
[`Kwargs`](../../../reference/api/#django_components.Component.Kwargs),
or [`Slots`](../../../reference/api/#django_components.Component.Slots) classes,
you may be inclined to define the defaults in the classes.
```py
class ProfileCard(Component):
class Kwargs(NamedTuple):
show_details: bool = True
```
This is **NOT recommended**, because:
- The defaults will NOT be applied to inputs when using [`self.raw_kwargs`](../../../reference/api/#django_components.Component.raw_kwargs) property.
- The defaults will NOT be applied when a field is given but set to `None`.
Instead, define the defaults in the [`Defaults`](../../../reference/api/#django_components.Component.Defaults) class.
### Default factories
For objects such as lists, dictionaries or other instances, you have to be careful - if you simply set a default value, this instance will be shared across all instances of the component!
@ -78,7 +98,7 @@ class MyTable(Component):
selected_items = [1, 2, 3]
```
To avoid this, you can use a factory function wrapped in `Default`.
To avoid this, you can use a factory function wrapped in [`Default`](../../../reference/api#django_components.Default).
```py
from django_components import Component, Default
@ -104,7 +124,7 @@ class MyTable(Component):
### Accessing defaults
Since the defaults are defined on the component class, you can access the defaults for a component with the `Component.Defaults` property.
Since the defaults are defined on the component class, you can access the defaults for a component with the [`Component.Defaults`](../../../reference/api#django_components.Component.Defaults) property.
So if we have a component like this:
@ -118,10 +138,10 @@ class MyTable(Component):
position = "left"
selected_items = Default(lambda: [1, 2, 3])
def get_context_data(self, position, selected_items):
def get_template_data(self, args, kwargs, slots, context):
return {
"position": position,
"selected_items": selected_items,
"position": kwargs["position"],
"selected_items": kwargs["selected_items"],
}
```

View file

@ -0,0 +1,151 @@
_New in version 0.34_
_Note: Since 0.92, `Component` is no longer a subclass of Django's `View`. Instead, the nested
[`Component.View`](../../../reference/api#django_components.Component.View) class is a subclass of Django's `View`._
---
For web applications, it's common to define endpoints that serve HTML content (AKA views).
django-components has a suite of features that help you write and manage views and their URLs:
- For each component, you can define methods for handling HTTP requests (GET, POST, etc.) - `get()`, `post()`, etc.
- Use [`Component.as_view()`](../../../reference/api#django_components.Component.as_view) to be able to use your Components with Django's [`urlpatterns`](https://docs.djangoproject.com/en/5.2/topics/http/urls/). This works the same way as [`View.as_view()`](https://docs.djangoproject.com/en/5.2/ref/class-based-views/base/#django.views.generic.base.View.as_view).
- To avoid having to manually define the endpoints for each component, you can set the component to be "public" with [`Component.View.public = True`](../../../reference/api#django_components.ComponentView.public). This will automatically create a URL for the component. To retrieve the component URL, use [`get_component_url()`](../../../reference/api#django_components.get_component_url).
- In addition, [`Component`](../../../reference/api#django_components.Component) has a [`render_to_response()`](../../../reference/api#django_components.Component.render_to_response) method that renders the component template based on the provided input and returns an `HttpResponse` object.
## Define handlers
Here's an example of a calendar component defined as a view. Simply define a `View` class with your custom `get()` method to handle GET requests:
```djc_py title="[project root]/components/calendar.py"
from django_components import Component, ComponentView, register
@register("calendar")
class Calendar(Component):
template = """
<div class="calendar-component">
<div class="header">
{% slot "header" / %}
</div>
<div class="body">
Today's date is <span>{{ date }}</span>
</div>
</div>
"""
class View:
# Handle GET requests
def get(self, request, *args, **kwargs):
# Return HttpResponse with the rendered content
return Calendar.render_to_response(
request=request,
kwargs={
"date": request.GET.get("date", "2020-06-06"),
},
slots={
"header": "Calendar header",
},
)
```
!!! info
The View class supports all the same HTTP methods as Django's [`View`](https://docs.djangoproject.com/en/5.2/ref/class-based-views/base/#django.views.generic.base.View) class. These are:
`get()`, `post()`, `put()`, `patch()`, `delete()`, `head()`, `options()`, `trace()`
Each of these receive the [`HttpRequest`](https://docs.djangoproject.com/en/5.2/ref/request-response/#django.http.HttpRequest) object as the first argument.
<!-- TODO_V1 REMOVE -->
!!! warning
**Deprecation warning:**
Previously, the handler methods such as `get()` and `post()` could be defined directly on the `Component` class:
```py
class Calendar(Component):
def get(self, request, *args, **kwargs):
return self.render_to_response(
kwargs={
"date": request.GET.get("date", "2020-06-06"),
}
)
```
This is deprecated from v0.137 onwards, and will be removed in v1.0.
### Acccessing component class
You can access the component class from within the View methods by using the [`View.component_cls`](../../../reference/api#django_components.ComponentView.component_cls) attribute:
```py
class Calendar(Component):
...
class View:
def get(self, request):
return self.component_cls.render_to_response(request=request)
```
## Register URLs manually
To register the component as a route / endpoint in Django, add an entry to your
[`urlpatterns`](https://docs.djangoproject.com/en/5.2/topics/http/urls/).
In place of the view function, create a view object with [`Component.as_view()`](../../../reference/api#django_components.Component.as_view):
```python title="[project root]/urls.py"
from django.urls import path
from components.calendar.calendar import Calendar
urlpatterns = [
path("calendar/", Calendar.as_view()),
]
```
[`Component.as_view()`](../../../reference/api#django_components.Component.as_view)
internally calls [`View.as_view()`](https://docs.djangoproject.com/en/5.2/ref/class-based-views/base/#django.views.generic.base.View.as_view), passing the component
instance as one of the arguments.
## Register URLs automatically
If you don't care about the exact URL of the component, you can let django-components manage the URLs for you by setting the [`Component.View.public`](../../../reference/api#django_components.ComponentView.public) attribute to `True`:
```py
class MyComponent(Component):
class View:
public = True
def get(self, request):
return self.component_cls.render_to_response(request=request)
...
```
Then, to get the URL for the component, use [`get_component_url()`](../../../reference/api#django_components.get_component_url):
```py
from django_components import get_component_url
url = get_component_url(MyComponent)
```
This way you don't have to mix your app URLs with component URLs.
!!! info
If you need to pass query parameters or a fragment to the component URL, you can do so by passing the `query` and `fragment` arguments to [`get_component_url()`](../../../reference/api#django_components.get_component_url):
```py
url = get_component_url(
MyComponent,
query={"foo": "bar"},
fragment="baz",
)
# /components/ext/view/components/c1ab2c3?foo=bar#baz
```

View file

@ -1,148 +0,0 @@
_New in version 0.34_
_Note: Since 0.92, Component no longer subclasses View. To configure the View class, set the nested `Component.View` class_
Components can now be used as views:
- Components define the `Component.as_view()` class method that can be used the same as [`View.as_view()`](https://docs.djangoproject.com/en/5.1/ref/class-based-views/base/#django.views.generic.base.View.as_view).
- By default, you can define GET, POST or other HTTP handlers directly on the Component, same as you do with [View](https://docs.djangoproject.com/en/5.1/ref/class-based-views/base/#view). For example, you can override `get` and `post` to handle GET and POST requests, respectively.
- In addition, `Component` now has a [`render_to_response`](#inputs-of-render-and-render_to_response) method that renders the component template based on the provided context and slots' data and returns an `HttpResponse` object.
## Component as view example
Here's an example of a calendar component defined as a view:
```djc_py
# In a file called [project root]/components/calendar.py
from django_components import Component, ComponentView, register
@register("calendar")
class Calendar(Component):
template = """
<div class="calendar-component">
<div class="header">
{% slot "header" / %}
</div>
<div class="body">
Today's date is <span>{{ date }}</span>
</div>
</div>
"""
# Handle GET requests
def get(self, request, *args, **kwargs):
context = {
"date": request.GET.get("date", "2020-06-06"),
}
slots = {
"header": "Calendar header",
}
# Return HttpResponse with the rendered content
return self.render_to_response(
context=context,
slots=slots,
)
```
Then, to use this component as a view, you should create a `urls.py` file in your components directory, and add a path to the component's view:
```python
# In a file called [project root]/components/urls.py
from django.urls import path
from components.calendar.calendar import Calendar
urlpatterns = [
path("calendar/", Calendar.as_view()),
]
```
`Component.as_view()` is a shorthand for calling [`View.as_view()`](https://docs.djangoproject.com/en/5.1/ref/class-based-views/base/#django.views.generic.base.View.as_view) and passing the component
instance as one of the arguments.
Remember to add `__init__.py` to your components directory, so that Django can find the `urls.py` file.
Finally, include the component's urls in your project's `urls.py` file:
```python
# In a file called [project root]/urls.py
from django.urls import include, path
urlpatterns = [
path("components/", include("components.urls")),
]
```
Note: Slots content are automatically escaped by default to prevent XSS attacks. To disable escaping, set `escape_slots_content=False` in the `render_to_response` method. If you do so, you should make sure that any content you pass to the slots is safe, especially if it comes from user input.
If you're planning on passing an HTML string, check Django's use of [`format_html`](https://docs.djangoproject.com/en/5.0/ref/utils/#django.utils.html.format_html) and [`mark_safe`](https://docs.djangoproject.com/en/5.0/ref/utils/#django.utils.safestring.mark_safe).
## Modifying the View class
The View class that handles the requests is defined on `Component.View`.
When you define a GET or POST handlers on the `Component` class, like so:
```py
class MyComponent(Component):
def get(self, request, *args, **kwargs):
return self.render_to_response(
context={
"date": request.GET.get("date", "2020-06-06"),
},
)
def post(self, request, *args, **kwargs) -> HttpResponse:
variable = request.POST.get("variable")
return self.render_to_response(
kwargs={"variable": variable}
)
```
Then the request is still handled by `Component.View.get()` or `Component.View.post()`
methods. However, by default, `Component.View.get()` points to `Component.get()`, and so on.
```py
class ComponentView(View):
component: Component = None
...
def get(self, request, *args, **kwargs):
return self.component.get(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
return self.component.post(request, *args, **kwargs)
...
```
If you want to define your own `View` class, you need to:
1. Set the class as `Component.View`
2. Subclass from `ComponentView`, so the View instance has access to the component instance.
In the example below, we added extra logic into `View.setup()`.
Note that the POST handler is still defined at the top. This is because `View` subclasses `ComponentView`, which defines the `post()` method that calls `Component.post()`.
If you were to overwrite the `View.post()` method, then `Component.post()` would be ignored.
```py
from django_components import Component, ComponentView
class MyComponent(Component):
def post(self, request, *args, **kwargs) -> HttpResponse:
variable = request.POST.get("variable")
return self.component.render_to_response(
kwargs={"variable": variable}
)
class View(ComponentView):
def setup(self, request, *args, **kwargs):
super(request, *args, **kwargs)
do_something_extra(request, *args, **kwargs)
```

View file

@ -1,134 +0,0 @@
_New in version 0.81_
Components can be rendered outside of Django templates, calling them as regular functions ("React-style").
The component class defines `render` and `render_to_response` class methods. These methods accept positional args, kwargs, and slots, offering the same flexibility as the `{% component %}` tag:
```djc_py
class SimpleComponent(Component):
template = """
{% load component_tags %}
hello: {{ hello }}
foo: {{ foo }}
kwargs: {{ kwargs|safe }}
slot_first: {% slot "first" required / %}
"""
def get_context_data(self, arg1, arg2, **kwargs):
return {
"hello": arg1,
"foo": arg2,
"kwargs": kwargs,
}
rendered = SimpleComponent.render(
args=["world", "bar"],
kwargs={"kw1": "test", "kw2": "ooo"},
slots={"first": "FIRST_SLOT"},
context={"from_context": 98},
)
```
Renders:
```
hello: world
foo: bar
kwargs: {'kw1': 'test', 'kw2': 'ooo'}
slot_first: FIRST_SLOT
```
## Inputs of `render` and `render_to_response`
Both `render` and `render_to_response` accept the same input:
```py
Component.render(
context: Mapping | django.template.Context | None = None,
args: List[Any] | None = None,
kwargs: Dict[str, Any] | None = None,
slots: Dict[str, str | SafeString | SlotFunc] | None = None,
escape_slots_content: bool = True
) -> str:
```
- _`args`_ - Positional args for the component. This is the same as calling the component
as `{% component "my_comp" arg1 arg2 ... %}`
- _`kwargs`_ - Keyword args for the component. This is the same as calling the component
as `{% component "my_comp" key1=val1 key2=val2 ... %}`
- _`slots`_ - Component slot fills. This is the same as pasing `{% fill %}` tags to the component.
Accepts a dictionary of `{ slot_name: slot_content }` where `slot_content` can be a string
or [`SlotFunc`](#slotfunc).
- _`escape_slots_content`_ - Whether the content from `slots` should be escaped. `True` by default to prevent XSS attacks. If you disable escaping, you should make sure that any content you pass to the slots is safe, especially if it comes from user input.
- _`context`_ - A context (dictionary or Django's Context) within which the component
is rendered. The keys on the context can be accessed from within the template.
- NOTE: In "isolated" mode, context is NOT accessible, and data MUST be passed via
component's args and kwargs.
- _`request`_ - A Django request object. This is used to enable Django template `context_processors` to run,
allowing for template tags like `{% csrf_token %}` and variables like `{{ debug }}`.
- Similar behavior can be achieved with [provide / inject](#how-to-use-provide--inject).
- This is used internally to convert `context` to a RequestContext. It does nothing if `context` is already
a `Context` instance.
### `SlotFunc`
When rendering components with slots in `render` or `render_to_response`, you can pass either a string or a function.
The function has following signature:
```py
def render_func(
context: Context,
data: Dict[str, Any],
slot_ref: SlotRef,
) -> str | SafeString:
return nodelist.render(ctx)
```
- _`context`_ - Django's Context available to the Slot Node.
- _`data`_ - Data passed to the `{% slot %}` tag. See [Scoped Slots](#scoped-slots).
- _`slot_ref`_ - The default slot content. See [Accessing original content of slots](#accessing-original-content-of-slots).
- NOTE: The slot is lazily evaluated. To render the slot, convert it to string with `str(slot_ref)`.
Example:
```py
def footer_slot(ctx, data, slot_ref):
return f"""
SLOT_DATA: {data['abc']}
ORIGINAL: {slot_ref}
"""
MyComponent.render_to_response(
slots={
"footer": footer_slot,
},
)
```
## Response class of `render_to_response`
While `render` method returns a plain string, `render_to_response` wraps the rendered content in a "Response" class. By default, this is `django.http.HttpResponse`.
If you want to use a different Response class in `render_to_response`, set the `Component.response_class` attribute:
```py
class MyResponse(HttpResponse):
def __init__(self, *args, **kwargs) -> None:
super().__init__(*args, **kwargs)
# Configure response
self.headers = ...
self.status = ...
class SimpleComponent(Component):
response_class = MyResponse
template: types.django_html = "HELLO"
response = SimpleComponent.render_to_response()
assert isinstance(response, MyResponse)
```

View file

@ -47,10 +47,10 @@ You can use this for example to allow users of your component to add extra attri
```djc_py
@register("my_comp")
class MyComp(Component):
# Capture extra kwargs in `attrs`
def get_context_data(self, **attrs):
# Pass all kwargs as `attrs`
def get_template_data(self, args, kwargs, slots, context):
return {
"attrs": attrs,
"attrs": kwargs,
"classes": "text-red",
"my_id": 123,
}
@ -329,7 +329,7 @@ Renders:
<div class="my-class extra-class"></div>
```
### Merging `style` Attributes
### Merging `style` attributes
The `style` attribute can be specified as a string of style properties as usual.
@ -364,8 +364,8 @@ If you want granular control over individual style properties, you can use a dic
If a style property is specified multiple times, the last value is used.
- If the last time the property is set is `False`, the property is removed.
- Properties set to `None` are ignored.
- If the last non-`None` instance of the property is set to `False`, the property is removed.
**Example:**
@ -607,10 +607,11 @@ class MyComp(Component):
</div>
"""
def get_context_data(self, date: Date, attrs: dict):
def get_template_data(self, args, kwargs, slots, context):
date = kwargs.pop("date")
return {
"date": date,
"attrs": attrs,
"attrs": kwargs,
"class_from_var": "extra-class"
}
@ -625,7 +626,7 @@ class Parent(Component):
/ %}
"""
def get_context_data(self, date: Date):
def get_template_data(self, args, kwargs, slots, context):
return {
"date": datetime.now(),
"json_data": json.dumps({"value": 456})
@ -669,7 +670,7 @@ So all kwargs that start with `attrs:` will be collected into an `attrs` dict.
attrs:@click="(e) => onClick(e, 'from_parent')"
```
And `get_context_data` of `MyComp` will receive `attrs` input with following keys:
And `get_template_data` of `MyComp` will receive a kwarg named `attrs` with following keys:
```py
attrs = {

View file

@ -0,0 +1,441 @@
## Overview
Each component can have single "primary" HTML, CSS and JS file associated with them.
Each of these can be either defined inline, or in a separate file:
- HTML files are defined using [`Component.template`](../../reference/api.md#django_components.Component.template) or [`Component.template_file`](../../reference/api.md#django_components.Component.template_file)
- CSS files are defined using [`Component.css`](../../reference/api.md#django_components.Component.css) or [`Component.css_file`](../../reference/api.md#django_components.Component.css_file)
- JS files are defined using [`Component.js`](../../reference/api.md#django_components.Component.js) or [`Component.js_file`](../../reference/api.md#django_components.Component.js_file)
```py
@register("calendar")
class Calendar(Component):
template_file = "calendar.html"
css_file = "calendar.css"
js_file = "calendar.js"
```
or
```djc_py
@register("calendar")
class Calendar(Component):
template = """
<div class="welcome">
Hi there!
</div>
"""
css = """
.welcome {
color: red;
}
"""
js = """
console.log("Hello, world!");
"""
```
These "primary" files will have special behavior. For example, each will receive variables from the component's data methods.
Read more about each file type below:
- [HTML](#html)
- [CSS](#css)
- [JS](#js)
In addition, you can define extra "secondary" CSS / JS files using the nested [`Component.Media`](../../reference/api.md#django_components.Component.Media) class,
by setting [`Component.Media.js`](../../reference/api.md#django_components.ComponentMediaInput.js) and [`Component.Media.css`](../../reference/api.md#django_components.ComponentMediaInput.css).
Single component can have many secondary files. There is no special behavior for them.
You can use these for third-party libraries, or for shared CSS / JS files.
Read more about [Secondary JS / CSS files](../secondary_js_css_files).
!!! warning
You **cannot** use both inlined code **and** separate file for a single language type (HTML, CSS, JS).
However, you can freely mix these for different languages:
```djc_py
class MyTable(Component):
template: types.django_html = """
<div class="welcome">
Hi there!
</div>
"""
js_file = "my_table.js"
css_file = "my_table.css"
```
## HTML
Components use Django's template system to define their HTML.
This means that you can use [Django's template syntax](https://docs.djangoproject.com/en/5.2/ref/templates/language/) to define your HTML.
Inside the template, you can access the data returned from the [`get_template_data()`](../../../reference/api/#django_components.Component.get_template_data) method.
You can define the HTML directly in your Python code using the [`template`](../../reference/api.md#django_components.Component.template) attribute:
```djc_py
class Button(Component):
template = """
<button class="btn">
{% if icon %}
<i class="{{ icon }}"></i>
{% endif %}
{{ text }}
</button>
"""
def get_template_data(self, args, kwargs, slots, context):
return {
"text": kwargs.get("text", "Click me"),
"icon": kwargs.get("icon", None),
}
```
Or you can define the HTML in a separate file and reference it using [`template_file`](../../reference/api.md#django_components.Component.template_file):
```python
class Button(Component):
template_file = "button.html"
def get_template_data(self, args, kwargs, slots, context):
return {
"text": kwargs.get("text", "Click me"),
"icon": kwargs.get("icon", None),
}
```
```django title="button.html"
<button class="btn">
{% if icon %}
<i class="{{ icon }}"></i>
{% endif %}
{{ text }}
</button>
```
### Dynamic templates
Each component has only a single template associated with it.
However, whether it's for A/B testing or for preserving public API
when sharing your components, sometimes you may need to render different templates
based on the input to your component.
You can use [`Component.on_render()`](../../reference/api.md#django_components.Component.on_render)
to dynamically override what template gets rendered.
By default, the component's template is rendered as-is.
```py
class Table(Component):
def on_render(self, context: Context, template: Optional[Template]):
if template is not None:
return template.render(context)
```
If you want to render a different template in its place,
we recommended you to:
1. Wrap the substitute templates as new Components
2. Then render those Components inside [`Component.on_render()`](../../reference/api.md#django_components.Component.on_render):
```py
class TableNew(Component):
template_file = "table_new.html"
class TableOld(Component):
template_file = "table_old.html"
class Table(Component):
def on_render(self, context, template):
if self.kwargs.get("feat_table_new_ui"):
return TableNew.render(
args=self.args,
kwargs=self.kwargs,
slots=self.slots,
)
else:
return TableOld.render(
args=self.args,
kwargs=self.kwargs,
slots=self.slots,
)
```
!!! warning
If you do not wrap the templates as Components,
there is a risk that some [extensions](../../advanced/extensions) will not work as expected.
```py
new_template = Template("""
{% load django_components %}
<div>
{% slot "content" %}
Other template
{% endslot %}
</div>
""")
class Table(Component):
def on_render(self, context, template):
if self.kwargs.get("feat_table_new_ui"):
return new_template.render(context)
else:
return template.render(context)
```
### Template-less components
Since you can use [`Component.on_render()`](../../reference/api.md#django_components.Component.on_render)
to render *other* components, there is no need to define a template for the component.
So even an empty component like this is valid:
```py
class MyComponent(Component):
pass
```
These "template-less" components can be useful as base classes for other components, or as mixins.
### HTML processing
Django Components expects the rendered template to be a valid HTML. This is needed to enable features like [CSS / JS variables](../html_js_css_variables).
Here is how the HTML is post-processed:
1. **Insert component ID**: Each root element in the rendered HTML automatically receives a `data-djc-id-cxxxxxx` attribute containing a unique component instance ID.
```html
<!-- Output HTML -->
<div class="card" data-djc-id-c1a2b3c>
...
</div>
<div class="backdrop" data-djc-id-c1a2b3c>
...
</div>
```
2. **Insert CSS ID**: If the component defines CSS variables through [`get_css_data()`](../../../reference/api/#django_components.Component.get_css_data), the root elements also receive a `data-djc-css-xxxxxx` attribute. This attribute links the element to its specific CSS variables.
```html
<!-- Output HTML -->
<div class="card" data-djc-id-c1a2b3c data-djc-css-d4e5f6>
<!-- Component content -->
</div>
```
3. **Insert JS and CSS**: After the HTML is rendered, Django Components handles inserting JS and CSS dependencies into the page based on the [dependencies rendering strategy](../rendering_components/#dependencies-rendering) (document, fragment, or inline).
For example, if your component contains the
[`{% component_js_dependencies %}`](../../reference/template_tags.md#component_js_dependencies)
or
[`{% component_css_dependencies %}`](../../reference/template_tags.md#component_css_dependencies)
tags, or the `<head>` and `<body>` elements, the JS and CSS scripts will be inserted into the HTML.
For more information on how JS and CSS dependencies are rendered, see [Rendering JS / CSS](../../advanced/rendering_js_css).
## JS
The component's JS script is executed in the browser:
- It is executed AFTER the "secondary" JS files from [`Component.Media.js`](../../reference/api.md#django_components.ComponentMediaInput.js) are loaded.
- The script is only executed once, even if there are multiple instances of the component on the page.
- Component JS scripts are executed in the order how they appeared in the template / HTML (top to bottom).
You can define the JS directly in your Python code using the [`js`](../../reference/api.md#django_components.Component.js) attribute:
```djc_py
class Button(Component):
js = """
console.log("Hello, world!");
"""
def get_js_data(self, args, kwargs, slots, context):
return {
"text": kwargs.get("text", "Click me"),
}
```
Or you can define the JS in a separate file and reference it using [`js_file`](../../reference/api.md#django_components.Component.js_file):
```python
class Button(Component):
js_file = "button.js"
def get_js_data(self, args, kwargs, slots, context):
return {
"text": kwargs.get("text", "Click me"),
}
```
```django title="button.js"
console.log("Hello, world!");
```
## CSS
You can define the CSS directly in your Python code using the [`css`](../../reference/api.md#django_components.Component.css) attribute:
```djc_py
class Button(Component):
css = """
.btn {
width: 100px;
color: var(--color);
}
"""
def get_css_data(self, args, kwargs, slots, context):
return {
"color": kwargs.get("color", "red"),
}
```
Or you can define the CSS in a separate file and reference it using [`css_file`](../../reference/api.md#django_components.Component.css_file):
```python
class Button(Component):
css_file = "button.css"
def get_css_data(self, args, kwargs, slots, context):
return {
"text": kwargs.get("text", "Click me"),
}
```
```django title="button.css"
.btn {
color: red;
}
```
## File paths
Compared to the [secondary JS / CSS files](../secondary_js_css_files), the definition of file paths for the main HTML / JS / CSS files is quite simple - just strings, without any lists, objects, or globs.
However, similar to the secondary JS / CSS files, you can specify the file paths [relative to the component's directory](../secondary_js_css_files/#relative-to-component).
So if you have a directory with following files:
```
[project root]/components/calendar/
├── calendar.html
├── calendar.css
├── calendar.js
└── calendar.py
```
You can define the component like this:
```py title="[project root]/components/calendar/calendar.py"
from django_components import Component, register
@register("calendar")
class Calendar(Component):
template_file = "calendar.html"
css_file = "calendar.css"
js_file = "calendar.js"
```
Assuming that
[`COMPONENTS.dirs`](../../reference/settings.md#django_components.app_settings.ComponentsSettings.dirs)
contains path `[project root]/components`, the example above is the same as writing out:
```py title="[project root]/components/calendar/calendar.py"
from django_components import Component, register
@register("calendar")
class Calendar(Component):
template_file = "calendar/template.html"
css_file = "calendar/style.css"
js_file = "calendar/script.js"
```
If the path cannot be resolved relative to the component, django-components will attempt
to resolve the path relative to the component directories, as set in
[`COMPONENTS.dirs`](../../reference/settings.md#django_components.app_settings.ComponentsSettings.dirs)
or
[`COMPONENTS.app_dirs`](../../reference/settings.md#django_components.app_settings.ComponentsSettings.app_dirs).
Read more about [file path resolution](../secondary_js_css_files/#relative-to-component).
## Access component definition
Component's HTML / CSS / JS is resolved and loaded lazily.
This means that, when you specify any of
[`template_file`](../../reference/api.md#django_components.Component.template_file),
[`js_file`](../../reference/api.md#django_components.Component.js_file),
[`css_file`](../../reference/api.md#django_components.Component.css_file),
or [`Media.js/css`](../../reference/api.md#django_components.Component.Media),
these file paths will be resolved only once you either:
1. Access any of the following attributes on the component:
- [`media`](../../reference/api.md#django_components.Component.media),
[`template`](../../reference/api.md#django_components.Component.template),
[`template_file`](../../reference/api.md#django_components.Component.template_file),
[`js`](../../reference/api.md#django_components.Component.js),
[`js_file`](../../reference/api.md#django_components.Component.js_file),
[`css`](../../reference/api.md#django_components.Component.css),
[`css_file`](../../reference/api.md#django_components.Component.css_file)
2. Render the component.
Once the component's media files have been loaded once, they will remain in-memory
on the Component class:
- HTML from [`Component.template_file`](../../reference/api.md#django_components.Component.template_file)
will be available under [`Component.template`](../../reference/api.md#django_components.Component.template)
- CSS from [`Component.css_file`](../../reference/api.md#django_components.Component.css_file)
will be available under [`Component.css`](../../reference/api.md#django_components.Component.css)
- JS from [`Component.js_file`](../../reference/api.md#django_components.Component.js_file)
will be available under [`Component.js`](../../reference/api.md#django_components.Component.js)
Thus, whether you define HTML via
[`Component.template_file`](../../reference/api.md#django_components.Component.template_file)
or [`Component.template`](../../reference/api.md#django_components.Component.template),
you can always access the HTML content under [`Component.template`](../../reference/api.md#django_components.Component.template).
And the same applies for JS and CSS.
**Example:**
```py
# When we create Calendar component, the files like `calendar/template.html`
# are not yet loaded!
@register("calendar")
class Calendar(Component):
template_file = "calendar/template.html"
css_file = "calendar/style.css"
js_file = "calendar/script.js"
class Media:
css = "calendar/style1.css"
js = "calendar/script2.js"
# It's only at this moment that django-components reads the files like `calendar/template.html`
print(Calendar.css)
# Output:
# .calendar {
# width: 200px;
# background: pink;
# }
```
!!! warning
**Do NOT modify HTML / CSS / JS after it has been loaded**
django-components assumes that the component's media files like `js_file` or `Media.js/css` are static.
If you need to dynamically change these media files, consider instead defining multiple Components.
Modifying these files AFTER the component has been loaded at best does nothing. However, this is
an untested behavior, which may lead to unexpected errors.

View file

@ -0,0 +1,496 @@
When a component recieves input through [`{% component %}`](../../../reference/template_tags/#component) tag,
or the [`Component.render()`](../../../reference/api/#django_components.Component.render) or [`Component.render_to_response()`](../../../reference/api/#django_components.Component.render_to_response) methods, you can define how the input is handled, and what variables will be available to the template, JavaScript and CSS.
## Overview
Django Components offers three key methods for passing variables to different parts of your component:
- [`get_template_data()`](../../../reference/api/#django_components.Component.get_template_data) - Provides variables to your HTML template
- [`get_js_data()`](../../../reference/api/#django_components.Component.get_js_data) - Provides variables to your JavaScript code
- [`get_css_data()`](../../../reference/api/#django_components.Component.get_css_data) - Provides variables to your CSS styles
These methods let you pre-process inputs before they're used in rendering.
Each method handles the data independently - you can define different data for the template, JS, and CSS.
```python
class ProfileCard(Component):
class Kwargs(NamedTuple):
user_id: int
show_details: bool
class Defaults:
show_details = True
def get_template_data(self, args, kwargs: Kwargs, slots, context):
user = User.objects.get(id=kwargs.user_id)
return {
"user": user,
"show_details": kwargs.show_details,
}
def get_js_data(self, args, kwargs: Kwargs, slots, context):
return {
"user_id": kwargs.user_id,
}
def get_css_data(self, args, kwargs: Kwargs, slots, context):
text_color = "red" if kwargs.show_details else "blue"
return {
"text_color": text_color,
}
```
## Template variables
The [`get_template_data()`](../../../reference/api/#django_components.Component.get_template_data) method is the primary way to provide variables to your HTML template. It receives the component inputs and returns a dictionary of data that will be available in the template.
If [`get_template_data()`](../../../reference/api/#django_components.Component.get_template_data) returns `None`, an empty dictionary will be used.
```python
class ProfileCard(Component):
template_file = "profile_card.html"
class Kwargs(NamedTuple):
user_id: int
show_details: bool
def get_template_data(self, args, kwargs: Kwargs, slots, context):
user = User.objects.get(id=kwargs.user_id)
# Process and transform inputs
return {
"user": user,
"show_details": kwargs.show_details,
"user_joined_days": (timezone.now() - user.date_joined).days,
}
```
In your template, you can then use these variables:
```django
<div class="profile-card">
<h2>{{ user.username }}</h2>
{% if show_details %}
<p>Member for {{ user_joined_days }} days</p>
<p>Email: {{ user.email }}</p>
{% endif %}
</div>
```
### Legacy `get_context_data()`
The [`get_context_data()`](../../../reference/api/#django_components.Component.get_context_data) method is the legacy way to provide variables to your HTML template. It serves the same purpose as [`get_template_data()`](../../../reference/api/#django_components.Component.get_template_data) - it receives the component inputs and returns a dictionary of data that will be available in the template.
However, [`get_context_data()`](../../../reference/api/#django_components.Component.get_context_data) has a few drawbacks:
- It does NOT receive the `slots` and `context` parameters.
- The `args` and `kwargs` parameters are given as variadic `*args` and `**kwargs` parameters. As such, they cannot be typed.
```python
class ProfileCard(Component):
template_file = "profile_card.html"
def get_context_data(self, user_id, show_details=False, *args, **kwargs):
user = User.objects.get(id=user_id)
return {
"user": user,
"show_details": show_details,
}
```
There is a slight difference between [`get_context_data()`](../../../reference/api/#django_components.Component.get_context_data) and [`get_template_data()`](../../../reference/api/#django_components.Component.get_template_data)
when rendering a component with the [`{% component %}`](../../../reference/template_tags/#component) tag.
For example if you have component that accepts kwarg `date`:
```py
class MyComponent(Component):
def get_context_data(self, date, *args, **kwargs):
return {
"date": date,
}
def get_template_data(self, args, kwargs, slots, context):
return {
"date": kwargs["date"],
}
```
The difference is that:
- With [`get_context_data()`](../../../reference/api/#django_components.Component.get_context_data), you can pass `date` either as arg or kwarg:
```django
{% component "my_component" date=some_date %}
{% component "my_component" some_date %}
```
- But with [`get_template_data()`](../../../reference/api/#django_components.Component.get_template_data), `date` MUST be passed as kwarg:
```django
{% component "my_component" date=some_date %}
{% component "my_component" some_date %}
```
!!! warning
[`get_template_data()`](../../../reference/api/#django_components.Component.get_template_data)
and [`get_context_data()`](../../../reference/api/#django_components.Component.get_context_data)
are mutually exclusive.
If both methods return non-empty dictionaries, an error will be raised.
!!! note
The `get_context_data()` method will be removed in v2.
## Accessing component inputs
The component inputs are available in 3 ways:
### Function arguments
The data methods receive the inputs as parameters directly.
```python
class ProfileCard(Component):
# Access inputs directly as parameters
def get_template_data(self, args, kwargs, slots, context):
return {
"user_id": args[0],
"show_details": kwargs["show_details"],
}
```
!!! info
By default, the `args` parameter is a list, while `kwargs` and `slots` are dictionaries.
If you add typing to your component with
[`Args`](../../../reference/api/#django_components.Component.Args),
[`Kwargs`](../../../reference/api/#django_components.Component.Kwargs),
or [`Slots`](../../../reference/api/#django_components.Component.Slots) classes,
the respective inputs will be given as instances of these classes.
Learn more about [Component typing](../../fundamentals/typing_and_validation).
```py
class ProfileCard(Component):
class Args(NamedTuple):
user_id: int
class Kwargs(NamedTuple):
show_details: bool
# Access inputs directly as parameters
def get_template_data(self, args: Args, kwargs: Kwargs, slots, context):
return {
"user_id": args.user_id,
"show_details": kwargs.show_details,
}
```
### `args`, `kwargs`, `slots` properties
In other methods, you can access the inputs via
[`self.args`](../../../reference/api/#django_components.Component.args),
[`self.kwargs`](../../../reference/api/#django_components.Component.kwargs),
and [`self.slots`](../../../reference/api/#django_components.Component.slots) properties:
```py
class ProfileCard(Component):
def on_render_before(self, context: Context, template: Optional[Template]):
# Access inputs via self.args, self.kwargs, self.slots
self.args[0]
self.kwargs.get("show_details", False)
self.slots["footer"]
```
!!! info
These properties work the same way as `args`, `kwargs`, and `slots` parameters in the data methods:
By default, the `args` property is a list, while `kwargs` and `slots` are dictionaries.
If you add typing to your component with
[`Args`](../../../reference/api/#django_components.Component.Args),
[`Kwargs`](../../../reference/api/#django_components.Component.Kwargs),
or [`Slots`](../../../reference/api/#django_components.Component.Slots) classes,
the respective inputs will be given as instances of these classes.
Learn more about [Component typing](../../fundamentals/typing_and_validation).
```py
class ProfileCard(Component):
class Args(NamedTuple):
user_id: int
class Kwargs(NamedTuple):
show_details: bool
def get_template_data(self, args: Args, kwargs: Kwargs, slots, context):
return {
"user_id": self.args.user_id,
"show_details": self.kwargs.show_details,
}
```
<!-- TODO_v1 - Remove -->
### `input` property (low-level)
!!! warning
The `input` property is deprecated and will be removed in v1.
Instead, use properties defined on the
[`Component`](../../../reference/api/#django_components.Component) class
directly like
[`self.context`](../../../reference/api/#django_components.Component.context).
To access the unmodified inputs, use
[`self.raw_args`](../../../reference/api/#django_components.Component.raw_args),
[`self.raw_kwargs`](../../../reference/api/#django_components.Component.raw_kwargs),
and [`self.raw_slots`](../../../reference/api/#django_components.Component.raw_slots) properties.
The previous two approaches allow you to access only the most important inputs.
There are additional settings that may be passed to components.
If you need to access these, you can use [`self.input`](../../../reference/api/#django_components.Component.input) property
for a low-level access to all the inputs.
The `input` property contains all the inputs passed to the component (instance of [`ComponentInput`](../../../reference/api/#django_components.ComponentInput)).
This includes:
- [`input.args`](../../../reference/api/#django_components.ComponentInput.args) - List of positional arguments
- [`input.kwargs`](../../../reference/api/#django_components.ComponentInput.kwargs) - Dictionary of keyword arguments
- [`input.slots`](../../../reference/api/#django_components.ComponentInput.slots) - Dictionary of slots. Values are normalized to [`Slot`](../../../reference/api/#django_components.Slot) instances
- [`input.context`](../../../reference/api/#django_components.ComponentInput.context) - [`Context`](https://docs.djangoproject.com/en/5.2/ref/templates/api/#django.template.Context) object that should be used to render the component
- [`input.type`](../../../reference/api/#django_components.ComponentInput.type) - The type of the component (document, fragment)
- [`input.render_dependencies`](../../../reference/api/#django_components.ComponentInput.render_dependencies) - Whether to render dependencies (CSS, JS)
```python
class ProfileCard(Component):
def get_template_data(self, args, kwargs, slots, context):
# Access positional arguments
user_id = self.input.args[0] if self.input.args else None
# Access keyword arguments
show_details = self.input.kwargs.get("show_details", False)
# Render component differently depending on the type
if self.input.type == "fragment":
...
return {
"user_id": user_id,
"show_details": show_details,
}
```
!!! info
Unlike the parameters passed to the data methods, the `args`, `kwargs`, and `slots` in `self.input` property are always lists and dictionaries,
regardless of whether you added typing classes to your component (like [`Args`](../../../reference/api/#django_components.Component.Args),
[`Kwargs`](../../../reference/api/#django_components.Component.Kwargs),
or [`Slots`](../../../reference/api/#django_components.Component.Slots)).
## Default values
You can use [`Defaults`](../../../reference/api/#django_components.Component.Defaults) class to provide default values for your inputs.
These defaults will be applied either when:
- The input is not provided at rendering time
- The input is provided as `None`
When you then access the inputs in your data methods, the default values will be already applied.
Read more about [Component Defaults](./component_defaults.md).
```py
from django_components import Component, Default, register
@register("profile_card")
class ProfileCard(Component):
class Kwargs(NamedTuple):
show_details: bool
class Defaults:
show_details = True
# show_details will be set to True if `None` or missing
def get_template_data(self, args, kwargs: Kwargs, slots, context):
return {
"show_details": kwargs.show_details,
}
...
```
!!! warning
When typing your components with [`Args`](../../../reference/api/#django_components.Component.Args),
[`Kwargs`](../../../reference/api/#django_components.Component.Kwargs),
or [`Slots`](../../../reference/api/#django_components.Component.Slots) classes,
you may be inclined to define the defaults in the classes.
```py
class ProfileCard(Component):
class Kwargs(NamedTuple):
show_details: bool = True
```
This is **NOT recommended**, because:
- The defaults will NOT be applied to inputs when using [`self.raw_kwargs`](../../../reference/api/#django_components.Component.raw_kwargs) property.
- The defaults will NOT be applied when a field is given but set to `None`.
Instead, define the defaults in the [`Defaults`](../../../reference/api/#django_components.Component.Defaults) class.
## Accessing Render API
All three data methods have access to the Component's [Render API](../render_api), which includes:
- [`self.args`](../render_api/#args) - The positional arguments for the current render call
- [`self.kwargs`](../render_api/#kwargs) - The keyword arguments for the current render call
- [`self.slots`](../render_api/#slots) - The slots for the current render call
- [`self.raw_args`](../render_api/#args) - Unmodified positional arguments for the current render call
- [`self.raw_kwargs`](../render_api/#kwargs) - Unmodified keyword arguments for the current render call
- [`self.raw_slots`](../render_api/#slots) - Unmodified slots for the current render call
- [`self.context`](../render_api/#context) - The context for the current render call
- [`self.id`](../render_api/#component-id) - The unique ID for the current render call
- [`self.request`](../render_api/#request-and-context-processors) - The request object
- [`self.context_processors_data`](../render_api/#request-and-context-processors) - Data from Django's context processors
- [`self.inject()`](../render_api/#provide-inject) - Inject data into the component
- [`self.registry`](../render_api/#template-tag-metadata) - The [`ComponentRegistry`](../../../reference/api/#django_components.ComponentRegistry) instance
- [`self.registered_name`](../render_api/#template-tag-metadata) - The name under which the component was registered
- [`self.outer_context`](../render_api/#template-tag-metadata) - The context outside of the [`{% component %}`](../../../reference/template_tags#component) tag
- `self.deps_strategy` - The strategy for rendering dependencies
## Type hints
### Typing inputs
You can add type hints for the component inputs to ensure that the component logic is correct.
For this, define the [`Args`](../../../reference/api/#django_components.Component.Args),
[`Kwargs`](../../../reference/api/#django_components.Component.Kwargs),
and [`Slots`](../../../reference/api/#django_components.Component.Slots) classes,
and then add type hints to the data methods.
This will also validate the inputs at runtime, as the type classes will be instantiated with the inputs.
Read more about [Component typing](../../fundamentals/typing_and_validation).
```python
from typing import NamedTuple, Optional
from django_components import Component, SlotInput
class Button(Component):
class Args(NamedTuple):
name: str
class Kwargs(NamedTuple):
surname: str
maybe_var: Optional[int] = None # May be omitted
class Slots(NamedTuple):
my_slot: Optional[SlotInput] = None
footer: SlotInput
# Use the above classes to add type hints to the data method
def get_template_data(self, args: Args, kwargs: Kwargs, slots: Slots, context: Context):
# The parameters are instances of the classes we defined
assert isinstance(args, Button.Args)
assert isinstance(kwargs, Button.Kwargs)
assert isinstance(slots, Button.Slots)
```
!!! note
To access "untyped" inputs, use [`self.raw_args`](../../../reference/api/#django_components.Component.raw_args),
[`self.raw_kwargs`](../../../reference/api/#django_components.Component.raw_kwargs),
and [`self.raw_slots`](../../../reference/api/#django_components.Component.raw_slots) properties.
These are plain lists and dictionaries, even when you added typing to your component.
### Typing data
In the same fashion, you can add types and validation for the data that should be RETURNED from each data method.
For this, set the [`TemplateData`](../../../reference/api/#django_components.Component.TemplateData),
[`JsData`](../../../reference/api/#django_components.Component.JsData),
and [`CssData`](../../../reference/api/#django_components.Component.CssData) classes on the component class.
For each data method, you can either return a plain dictionary with the data, or an instance of the respective data class.
```python
from typing import NamedTuple
from django_components import Component
class Button(Component):
class TemplateData(NamedTuple):
data1: str
data2: int
class JsData(NamedTuple):
js_data1: str
js_data2: int
class CssData(NamedTuple):
css_data1: str
css_data2: int
def get_template_data(self, args, kwargs, slots, context):
return Button.TemplateData(
data1="...",
data2=123,
)
def get_js_data(self, args, kwargs, slots, context):
return Button.JsData(
js_data1="...",
js_data2=123,
)
def get_css_data(self, args, kwargs, slots, context):
return Button.CssData(
css_data1="...",
css_data2=123,
)
```
## Pass-through kwargs
It's best practice to explicitly define what args and kwargs a component accepts.
However, if you want a looser setup, you can easily write components that accept any number
of kwargs, and pass them all to the template
(similar to [django-cotton](https://github.com/wrabit/django-cotton)).
To do that, simply return the `kwargs` dictionary itself from [`get_template_data()`](../../../reference/api/#django_components.Component.get_template_data):
```py
class MyComponent(Component):
def get_template_data(self, args, kwargs, slots, context):
return kwargs
```
You can do the same for [`get_js_data()`](../../../reference/api/#django_components.Component.get_js_data) and [`get_css_data()`](../../../reference/api/#django_components.Component.get_css_data), if needed:
```py
class MyComponent(Component):
def get_js_data(self, args, kwargs, slots, context):
return kwargs
def get_css_data(self, args, kwargs, slots, context):
return kwargs
```

View file

@ -1,25 +1,22 @@
The most common use of django-components is to render HTML for a given request. As such,
The most common use of django-components is to render HTML when the server receives a request. As such,
there are a few features that are dependent on the request object.
## Passing and accessing HttpRequest
## Passing the HttpRequest object
In regular Django templates, the request object is available only within the `RequestContext`.
In regular Django templates, the request object is available only within the [`RequestContext`](https://docs.djangoproject.com/en/5.2/ref/templates/api/#django.template.RequestContext).
In Components, you can either use `RequestContext`, or pass the `request` object
In Components, you can either use [`RequestContext`](https://docs.djangoproject.com/en/5.2/ref/templates/api/#django.template.RequestContext), or pass the `request` object
explicitly to [`Component.render()`](../../../reference/api#django_components.Component.render) and
[`Component.render_to_response()`](../../../reference/api#django_components.Component.render_to_response).
When a component is nested in another, the child component uses parent's `request` object.
So the request object is available to components either when:
You can access the request object under [`Component.request`](../../../reference/api#django_components.Component.request):
- The component is rendered with [`RequestContext`](https://docs.djangoproject.com/en/5.2/ref/templates/api/#django.template.RequestContext) (Regular Django behavior)
- The component is rendered with a regular [`Context`](https://docs.djangoproject.com/en/5.2/ref/templates/api/#django.template.Context) (or none), but you set the `request` kwarg
of [`Component.render()`](../../../reference/api#django_components.Component.render).
- The component is nested and the parent has access to the request object.
```python
class MyComponent(Component):
def get_context_data(self):
return {
'user_id': self.request.GET['user_id'],
}
# ✅ With request
MyComponent.render(request=request)
MyComponent.render(context=RequestContext(request, {}))
@ -29,31 +26,7 @@ MyComponent.render()
MyComponent.render(context=Context({}))
```
## Context Processors
Components support Django's [context processors](https://docs.djangoproject.com/en/5.1/ref/templates/api/#using-requestcontext).
In regular Django templates, the context processors are applied only when the template is rendered with `RequestContext`.
Components allow you to pass the `request` object explicitly. Thus, the context processors are applied to components either when:
- The component is rendered with `RequestContext` (Regular Django behavior)
- The component is rendered with a regular `Context` (or none), but you set the `request` kwarg
of [`Component.render()`](../../../reference/api#django_components.Component.render).
- The component is nested in another component that matches one of the two conditions above.
```python
# ❌ No context processors
rendered = MyComponent.render()
rendered = MyComponent.render(Context({}))
# ✅ With context processors
rendered = MyComponent.render(request=request)
rendered = MyComponent.render(Context({}), request=request)
rendered = MyComponent.render(RequestContext(request, {}))
```
When a component is rendered within a template with [`{% component %}`](../../../reference/template_tags#component) tag, context processors are available depending on whether the template is rendered with `RequestContext` or not.
When a component is rendered within a template with [`{% component %}`](../../../reference/template_tags#component) tag, the request object is available depending on whether the template is rendered with [`RequestContext`](https://docs.djangoproject.com/en/5.2/ref/templates/api/#django.template.RequestContext) or not.
```python
template = Template("""
@ -62,13 +35,33 @@ template = Template("""
</div>
""")
# ❌ No context processors
# ❌ No request
rendered = template.render(Context({}))
# ✅ With context processors
# ✅ With request
rendered = template.render(RequestContext(request, {}))
```
## Accessing the HttpRequest object
When the component has access to the `request` object, the request object will be available in [`Component.request`](../../../reference/api/#django_components.Component.request).
```python
class MyComponent(Component):
def get_template_data(self, args, kwargs, slots, context):
return {
'user_id': self.request.GET['user_id'],
}
```
## Context Processors
Components support Django's [context processors](https://docs.djangoproject.com/en/5.2/ref/templates/api/#using-requestcontext).
In regular Django templates, the context processors are applied only when the template is rendered with [`RequestContext`](https://docs.djangoproject.com/en/5.2/ref/templates/api/#django.template.RequestContext).
In Components, the context processors are applied when the component has access to the `request` object.
### Accessing context processors data
The data from context processors is automatically available within the component's template.
@ -84,13 +77,21 @@ class MyComponent(Component):
MyComponent.render(request=request)
```
You can also access the context processors data from within [`get_context_data()`](../../../reference/api#django_components.Component.get_context_data) and other methods under [`Component.context_processors_data`](../../../reference/api#django_components.Component.context_processors_data).
You can also access the context processors data from within [`get_template_data()`](../../../reference/api#django_components.Component.get_template_data) and other methods under [`Component.context_processors_data`](../../../reference/api#django_components.Component.context_processors_data).
```python
class MyComponent(Component):
def get_context_data(self):
def get_template_data(self, args, kwargs, slots, context):
csrf_token = self.context_processors_data['csrf_token']
return {
'csrf_token': csrf_token,
}
```
This is a dictionary with the context processors data.
If the request object is not available, then [`self.context_processors_data`](../../../reference/api/#django_components.Component.context_processors_data) will be an empty dictionary.
!!! warning
The [`self.context_processors_data`](../../../reference/api/#django_components.Component.context_processors_data) object is generated dynamically, so changes to it are not persisted.

View file

@ -0,0 +1,376 @@
When a component is being rendered, whether with [`Component.render()`](../../../reference/api#django_components.Component.render)
or [`{% component %}`](../../../reference/template_tags#component), a component instance is populated with the current inputs and context. This allows you to access things like component inputs.
We refer to these render-time-only methods and attributes as the "Render API".
Render API is available inside these [`Component`](../../../reference/api#django_components.Component) methods:
- [`get_template_data()`](../../../reference/api#django_components.Component.get_template_data)
- [`get_js_data()`](../../../reference/api#django_components.Component.get_js_data)
- [`get_css_data()`](../../../reference/api#django_components.Component.get_css_data)
- [`get_context_data()`](../../../reference/api#django_components.Component.get_context_data)
- [`on_render_before()`](../../../reference/api#django_components.Component.on_render_before)
- [`on_render()`](../../../reference/api#django_components.Component.on_render)
- [`on_render_after()`](../../../reference/api#django_components.Component.on_render_after)
Example:
```python
class Table(Component):
def on_render_before(self, context, template):
# Access component's ID
assert self.id == "c1A2b3c"
# Access component's inputs, slots and context
assert self.args == [123, "str"]
assert self.kwargs == {"variable": "test", "another": 1}
footer_slot = self.slots["footer"]
some_var = self.context["some_var"]
def get_template_data(self, args, kwargs, slots, context):
# Access the request object and Django's context processors, if available
assert self.request.GET == {"query": "something"}
assert self.context_processors_data['user'].username == "admin"
rendered = Table.render(
kwargs={"variable": "test", "another": 1},
args=(123, "str"),
slots={"footer": "MY_SLOT"},
)
```
## Overview
The Render API includes:
- Component inputs:
- [`self.args`](../render_api/#args) - The positional arguments for the current render call
- [`self.kwargs`](../render_api/#kwargs) - The keyword arguments for the current render call
- [`self.slots`](../render_api/#slots) - The slots for the current render call
- [`self.raw_args`](../render_api/#args) - Unmodified positional arguments for the current render call
- [`self.raw_kwargs`](../render_api/#kwargs) - Unmodified keyword arguments for the current render call
- [`self.raw_slots`](../render_api/#slots) - Unmodified slots for the current render call
- [`self.context`](../render_api/#context) - The context for the current render call
- [`self.deps_strategy`](../../advanced/rendering_js_css#dependencies-strategies) - The strategy for rendering dependencies
- Request-related:
- [`self.request`](../render_api/#request-and-context-processors) - The request object (if available)
- [`self.context_processors_data`](../render_api/#request-and-context-processors) - Data from Django's context processors
- Provide / inject:
- [`self.inject()`](../render_api/#provide-inject) - Inject data into the component
- Template tag metadata:
- [`self.node`](../render_api/#template-tag-metadata) - The [`ComponentNode`](../../../reference/api/#django_components.ComponentNode) instance
- [`self.registry`](../render_api/#template-tag-metadata) - The [`ComponentRegistry`](../../../reference/api/#django_components.ComponentRegistry) instance
- [`self.registered_name`](../render_api/#template-tag-metadata) - The name under which the component was registered
- [`self.outer_context`](../render_api/#template-tag-metadata) - The context outside of the [`{% component %}`](../../../reference/template_tags#component) tag
- Other metadata:
- [`self.id`](../render_api/#component-id) - The unique ID for the current render call
## Component inputs
### Args
The `args` argument as passed to
[`Component.get_template_data()`](../../../reference/api/#django_components.Component.get_template_data).
If you defined the [`Component.Args`](../../../reference/api/#django_components.Component.Args) class,
then the [`Component.args`](../../../reference/api/#django_components.Component.args) property will return an instance of that class.
Otherwise, `args` will be a plain list.
Use [`self.raw_args`](../../../reference/api/#django_components.Component.raw_args)
to access the positional arguments as a plain list irrespective of [`Component.Args`](../../../reference/api/#django_components.Component.Args).
**Example:**
With `Args` class:
```python
from django_components import Component
class Table(Component):
class Args(NamedTuple):
page: int
per_page: int
def on_render_before(self, context: Context, template: Optional[Template]) -> None:
assert self.args.page == 123
assert self.args.per_page == 10
rendered = Table.render(
args=[123, 10],
)
```
Without `Args` class:
```python
from django_components import Component
class Table(Component):
def on_render_before(self, context: Context, template: Optional[Template]) -> None:
assert self.args[0] == 123
assert self.args[1] == 10
```
### Kwargs
The `kwargs` argument as passed to
[`Component.get_template_data()`](../../../reference/api/#django_components.Component.get_template_data).
If you defined the [`Component.Kwargs`](../../../reference/api/#django_components.Component.Kwargs) class,
then the [`Component.kwargs`](../../../reference/api/#django_components.Component.kwargs) property will return an instance of that class.
Otherwise, `kwargs` will be a plain dictionary.
Use [`self.raw_kwargs`](../../../reference/api/#django_components.Component.raw_kwargs)
to access the keyword arguments as a plain dictionary irrespective of [`Component.Kwargs`](../../../reference/api/#django_components.Component.Kwargs).
**Example:**
With `Kwargs` class:
```python
from django_components import Component
class Table(Component):
class Kwargs(NamedTuple):
page: int
per_page: int
def on_render_before(self, context: Context, template: Optional[Template]) -> None:
assert self.kwargs.page == 123
assert self.kwargs.per_page == 10
rendered = Table.render(
kwargs={"page": 123, "per_page": 10},
)
```
Without `Kwargs` class:
```python
from django_components import Component
class Table(Component):
def on_render_before(self, context: Context, template: Optional[Template]) -> None:
assert self.kwargs["page"] == 123
assert self.kwargs["per_page"] == 10
```
### Slots
The `slots` argument as passed to
[`Component.get_template_data()`](../../../reference/api/#django_components.Component.get_template_data).
If you defined the [`Component.Slots`](../../../reference/api/#django_components.Component.Slots) class,
then the [`Component.slots`](../../../reference/api/#django_components.Component.slots) property will return an instance of that class.
Otherwise, `slots` will be a plain dictionary.
Use [`self.raw_slots`](../../../reference/api/#django_components.Component.raw_slots)
to access the slots as a plain dictionary irrespective of [`Component.Slots`](../../../reference/api/#django_components.Component.Slots).
**Example:**
With `Slots` class:
```python
from django_components import Component, Slot, SlotInput
class Table(Component):
class Slots(NamedTuple):
header: SlotInput
footer: SlotInput
def on_render_before(self, context: Context, template: Optional[Template]) -> None:
assert isinstance(self.slots.header, Slot)
assert isinstance(self.slots.footer, Slot)
rendered = Table.render(
slots={
"header": "MY_HEADER",
"footer": lambda ctx: "FOOTER: " + ctx.data["user_id"],
},
)
```
Without `Slots` class:
```python
from django_components import Component, Slot, SlotInput
class Table(Component):
def on_render_before(self, context: Context, template: Optional[Template]) -> None:
assert isinstance(self.slots["header"], Slot)
assert isinstance(self.slots["footer"], Slot)
```
### Context
The `context` argument as passed to
[`Component.get_template_data()`](../../../reference/api/#django_components.Component.get_template_data).
This is Django's [Context](https://docs.djangoproject.com/en/5.2/ref/templates/api/#django.template.Context)
with which the component template is rendered.
If the root component or template was rendered with
[`RequestContext`](https://docs.djangoproject.com/en/5.2/ref/templates/api/#django.template.RequestContext)
then this will be an instance of `RequestContext`.
Whether the context variables defined in `context` are available to the template depends on the
[context behavior mode](../../../reference/settings#django_components.app_settings.ComponentsSettings.context_behavior):
- In `"django"` context behavior mode, the template will have access to the keys of this context.
- In `"isolated"` context behavior mode, the template will NOT have access to this context,
and data MUST be passed via component's args and kwargs.
## Component ID
Component ID (or render ID) is a unique identifier for the current render call.
That means that if you call [`Component.render()`](../../../reference/api#django_components.Component.render)
multiple times, the ID will be different for each call.
It is available as [`self.id`](../../../reference/api#django_components.Component.id).
The ID is a 7-letter alphanumeric string in the format `cXXXXXX`,
where `XXXXXX` is a random string of 6 alphanumeric characters (case-sensitive).
E.g. `c1a2b3c`.
A single render ID has a chance of collision 1 in 57 billion. However, due to birthday paradox, the chance of collision increases to 1% when approaching ~33K render IDs.
Thus, there is currently a soft-cap of ~30K components rendered on a single page.
If you need to expand this limit, please open an issue on GitHub.
```python
class Table(Component):
def get_template_data(self, args, kwargs, slots, context):
# Access component's ID
assert self.id == "c1A2b3c"
```
## Request and context processors
Components have access to the request object and context processors data if the component was:
- Given a [`request`](../../../reference/api/#django_components.Component.render) kwarg directly
- Rendered with [`RenderContext`](https://docs.djangoproject.com/en/5.2/ref/templates/api/#django.template.RequestContext)
- Nested in another component for which any of these conditions is true
Then the request object will be available in [`self.request`](../../../reference/api/#django_components.Component.request).
If the request object is available, you will also be able to access the [`context processors`](https://docs.djangoproject.com/en/5.2/ref/templates/api/#configuring-an-engine) data in [`self.context_processors_data`](../../../reference/api/#django_components.Component.context_processors_data).
This is a dictionary with the context processors data.
If the request object is not available, then [`self.context_processors_data`](../../../reference/api/#django_components.Component.context_processors_data) will be an empty dictionary.
Read more about the request object and context processors in the [HTTP Request](./http_request.md) section.
```python
from django.http import HttpRequest
class Table(Component):
def get_template_data(self, args, kwargs, slots, context):
# Access the request object and Django's context processors
assert self.request.GET == {"query": "something"}
assert self.context_processors_data['user'].username == "admin"
rendered = Table.render(
request=HttpRequest(),
)
```
## Provide / Inject
Components support a provide / inject system as known from Vue or React.
When rendering the component, you can call [`self.inject()`](../../../reference/api/#django_components.Component.inject) with the key of the data you want to inject.
The object returned by [`self.inject()`](../../../reference/api/#django_components.Component.inject)
To provide data to components, use the [`{% provide %}`](../../../reference/template_tags#provide) template tag.
Read more about [Provide / Inject](../advanced/provide_inject.md).
```python
class Table(Component):
def get_template_data(self, args, kwargs, slots, context):
# Access provided data
data = self.inject("some_data")
assert data.some_data == "some_data"
```
## Template tag metadata
If the component is rendered with [`{% component %}`](../../../reference/template_tags#component) template tag,
the following metadata is available:
- [`self.node`](../../../reference/api/#django_components.Component.node) - The [`ComponentNode`](../../../reference/api/#django_components.ComponentNode) instance
- [`self.registry`](../../../reference/api/#django_components.Component.registry) - The [`ComponentRegistry`](../../../reference/api/#django_components.ComponentRegistry) instance
that was used to render the component
- [`self.registered_name`](../../../reference/api/#django_components.Component.registered_name) - The name under which the component was registered
- [`self.outer_context`](../../../reference/api/#django_components.Component.outer_context) - The context outside of the [`{% component %}`](../../../reference/template_tags#component) tag
```django
{% with abc=123 %}
{{ abc }} {# <--- This is in outer context #}
{% component "my_component" / %}
{% endwith %}
```
You can use these to check whether the component was rendered inside a template with [`{% component %}`](../../../reference/template_tags#component) tag
or in Python with [`Component.render()`](../../../reference/api/#django_components.Component.render).
```python
class MyComponent(Component):
def get_template_data(self, args, kwargs, slots, context):
if self.registered_name is None:
# Do something for the render() function
else:
# Do something for the {% component %} template tag
```
You can access the [`ComponentNode`](../../../reference/api/#django_components.ComponentNode) under [`Component.node`](../../../reference/api/#django_components.Component.node):
```py
class MyComponent(Component):
def get_template_data(self, context, template):
if self.node is not None:
assert self.node.name == "my_component"
```
Accessing the [`ComponentNode`](../../../reference/api/#django_components.ComponentNode) is mostly useful for extensions, which can modify their behaviour based on the source of the Component.
For example, if `MyComponent` was used in another component - that is,
with a `{% component "my_component" %}` tag
in a template that belongs to another component - then you can use
[`self.node.template_component`](../../../reference/api/#django_components.ComponentNode.template_component)
to access the owner [`Component`](../../../reference/api/#django_components.Component) class.
```djc_py
class Parent(Component):
template: types.django_html = """
<div>
{% component "my_component" / %}
</div>
"""
@register("my_component")
class MyComponent(Component):
def get_template_data(self, context, template):
if self.node is not None:
assert self.node.template_component == Parent
```
!!! info
`Component.node` is `None` if the component is created by [`Component.render()`](../../../reference/api/#django_components.Component.render)
(but you can pass in the `node` kwarg yourself).

View file

@ -0,0 +1,627 @@
Your components can be rendered either within your Django templates, or directly in Python code.
## Overview
Django Components provides three main methods to render components:
- [`{% component %}` tag](#component-tag) - Renders the component within your Django templates
- [`Component.render()` method](#render-method) - Renders the component to a string
- [`Component.render_to_response()` method](#render-to-response-method) - Renders the component and wraps it in an HTTP response
## `{% component %}` tag
Use the [`{% component %}`](../../../reference/template_tags#component) tag to render a component within your Django templates.
The [`{% component %}`](../../../reference/template_tags#component) tag takes:
- Component's registered name as the first positional argument,
- Followed by any number of positional and keyword arguments.
```django
{% load component_tags %}
<div>
{% component "button" name="John" job="Developer" / %}
</div>
```
To pass in slots content, you can insert [`{% fill %}`](../../../reference/template_tags#fill) tags,
directly within the [`{% component %}`](../../../reference/template_tags#component) tag to "fill" the slots:
```django
{% component "my_table" rows=rows headers=headers %}
{% fill "pagination" %}
< 1 | 2 | 3 >
{% endfill %}
{% endcomponent %}
```
You can even nest [`{% fill %}`](../../../reference/template_tags#fill) tags within
[`{% if %}`](https://docs.djangoproject.com/en/5.2/ref/templates/builtins/#if),
[`{% for %}`](https://docs.djangoproject.com/en/5.2/ref/templates/builtins/#for)
and other tags:
```django
{% component "my_table" rows=rows headers=headers %}
{% if rows %}
{% fill "pagination" %}
< 1 | 2 | 3 >
{% endfill %}
{% endif %}
{% endcomponent %}
```
!!! info "Omitting the `component` keyword"
If you would like to omit the `component` keyword, and simply refer to your
components by their registered names:
```django
{% button name="John" job="Developer" / %}
```
You can do so by setting the "shorthand" [Tag formatter](../../advanced/tag_formatters) in the settings:
```python
# settings.py
COMPONENTS = {
"tag_formatter": "django_components.component_shorthand_formatter",
}
```
!!! info "Extended template tag syntax"
Unlike regular Django template tags, django-components' tags offer extra features like
defining literal lists and dicts, and more. Read more about [Template tag syntax](../template_tag_syntax).
### Registering components
For a component to be renderable with the [`{% component %}`](../../../reference/template_tags#component) tag, it must be first registered with the [`@register()`](../../../reference/api/#django_components.register) decorator.
For example, if you register a component under the name `"button"`:
```python
from typing import NamedTuple
from django_components import Component, register
@register("button")
class Button(Component):
template_file = "button.html"
class Kwargs(NamedTuple):
name: str
job: str
def get_template_data(self, args, kwargs, slots, context):
...
```
Then you can render this component by using its registered name `"button"` in the template:
```django
{% component "button" name="John" job="Developer" / %}
```
As you can see above, the args and kwargs passed to the [`{% component %}`](../../../reference/template_tags#component) tag correspond
to the component's input.
For more details, read [Registering components](../../advanced/component_registry).
!!! note "Why do I need to register components?"
TL;DR: To be able to share components as libraries, and because components can be registed with multiple registries / libraries.
Django-components allows to [share components across projects](../../advanced/component_libraries).
However, different projects may use different settings. For example, one project may prefer the "long" format:
```django
{% component "button" name="John" job="Developer" / %}
```
While the other may use the "short" format:
```django
{% button name="John" job="Developer" / %}
```
Both approaches are supported simultaneously for backwards compatibility, because django-components
started out with only the "long" format.
To avoid ambiguity, when you use a 3rd party library, it uses the syntax that the author
had configured for it.
So when you are creating a component, django-components need to know which registry the component
belongs to, so it knows which syntax to use.
### Rendering templates
If you have embedded the component in a Django template using the
[`{% component %}`](../../reference/template_tags#component) tag:
```django title="[project root]/templates/my_template.html"
{% load component_tags %}
<div>
{% component "calendar" date="2024-12-13" / %}
</div>
```
You can simply render the template with the Django's API:
- [`django.shortcuts.render()`](https://docs.djangoproject.com/en/5.2/topics/http/shortcuts/#render)
```python
from django.shortcuts import render
context = {"date": "2024-12-13"}
rendered_template = render(request, "my_template.html", context)
```
- [`Template.render()`](https://docs.djangoproject.com/en/5.2/ref/templates/api/#django.template.Template.render)
```python
from django.template import Template
from django.template.loader import get_template
# Either from a file
template = get_template("my_template.html")
# or inlined
template = Template("""
{% load component_tags %}
<div>
{% component "calendar" date="2024-12-13" / %}
</div>
""")
rendered_template = template.render()
```
### Isolating components
By default, components behave similarly to Django's
[`{% include %}`](https://docs.djangoproject.com/en/5.2/ref/templates/builtins/#include),
and the template inside the component has access to the variables defined in the outer template.
You can selectively isolate a component, using the `only` flag, so that the inner template
can access only the data that was explicitly passed to it:
```django
{% component "name" positional_arg keyword_arg=value ... only / %}
```
Alternatively, you can set all components to be isolated by default, by setting
[`context_behavior`](../../../reference/settings#django_components.app_settings.ComponentsSettings.context_behavior)
to `"isolated"` in your settings:
```python
# settings.py
COMPONENTS = {
"context_behavior": "isolated",
}
```
## `render()` method
The [`Component.render()`](../../../reference/api/#django_components.Component.render) method renders a component to a string.
This is the equivalent of calling the [`{% component %}`](../template_tags#component) tag.
```python
from typing import NamedTuple, Optional
from django_components import Component, SlotInput
class Button(Component):
template_file = "button.html"
class Args(NamedTuple):
name: str
class Kwargs(NamedTuple):
surname: str
age: int
class Slots(NamedTuple):
footer: Optional[SlotInput] = None
def get_template_data(self, args, kwargs, slots, context):
...
Button.render(
args=["John"],
kwargs={
"surname": "Doe",
"age": 30,
},
slots={
"footer": "i AM A SLOT",
},
)
```
[`Component.render()`](../../../reference/api/#django_components.Component.render) accepts the following arguments:
- `args` - Positional arguments to pass to the component (as a list or tuple)
- `kwargs` - Keyword arguments to pass to the component (as a dictionary)
- `slots` - Slot content to pass to the component (as a dictionary)
- `context` - Django context for rendering (can be a dictionary or a `Context` object)
- `deps_strategy` - [Dependencies rendering strategy](#dependencies-rendering) (default: `"document"`)
- `request` - [HTTP request object](../http_request), used for context processors (optional)
All arguments are optional. If not provided, they default to empty values or sensible defaults.
See the API reference for [`Component.render()`](../../../reference/api/#django_components.Component.render)
for more details on the arguments.
## `render_to_response()` method
The [`Component.render_to_response()`](../../../reference/api/#django_components.Component.render_to_response)
method works just like [`Component.render()`](../../../reference/api/#django_components.Component.render),
but wraps the result in an HTTP response.
It accepts all the same arguments as [`Component.render()`](../../../reference/api/#django_components.Component.render).
Any extra arguments are passed to the [`HttpResponse`](https://docs.djangoproject.com/en/5.2/ref/request-response/#django.http.HttpResponse)
constructor.
```python
from typing import NamedTuple, Optional
from django_components import Component, SlotInput
class Button(Component):
template_file = "button.html"
class Args(NamedTuple):
name: str
class Kwargs(NamedTuple):
surname: str
age: int
class Slots(NamedTuple):
footer: Optional[SlotInput] = None
def get_template_data(self, args, kwargs, slots, context):
...
# Render the component to an HttpResponse
response = Button.render_to_response(
args=["John"],
kwargs={
"surname": "Doe",
"age": 30,
},
slots={
"footer": "i AM A SLOT",
},
# Additional response arguments
status=200,
headers={"X-Custom-Header": "Value"},
)
```
This method is particularly useful in view functions, as you can return the result of the component directly:
```python
def profile_view(request, user_id):
return Button.render_to_response(
kwargs={
"surname": "Doe",
"age": 30,
},
request=request,
)
```
### Custom response classes
By default, [`Component.render_to_response()`](../../../reference/api/#django_components.Component.render_to_response)
returns a standard Django [`HttpResponse`](https://docs.djangoproject.com/en/5.2/ref/request-response/#django.http.HttpResponse).
You can customize this by setting the [`response_class`](../../../reference/api/#django_components.Component.response_class)
attribute on your component:
```python
from django.http import HttpResponse
from django_components import Component
class MyHttpResponse(HttpResponse):
...
class MyComponent(Component):
response_class = MyHttpResponse
response = MyComponent.render_to_response()
assert isinstance(response, MyHttpResponse)
```
## Dependencies rendering
The rendered HTML may be used in different contexts (browser, email, etc), and each may need different handling of JS and CSS scripts.
[`render()`](../../../reference/api/#django_components.Component.render) and [`render_to_response()`](../../../reference/api/#django_components.Component.render_to_response)
accept a `deps_strategy` parameter, which controls where and how the JS / CSS are inserted into the HTML.
The `deps_strategy` parameter is ultimately passed to [`render_dependencies()`](../../../reference/api/#django_components.render_dependencies).
Learn more about [Rendering JS / CSS](../../advanced/rendering_js_css).
There are six dependencies rendering strategies:
- [`document`](../../advanced/rendering_js_css#document) (default)
- Smartly inserts JS / CSS into placeholders ([`{% component_js_dependencies %}`](../../../reference/template_tags#component_js_dependencies)) or into `<head>` and `<body>` tags.
- Inserts extra script to allow `fragment` components to work.
- Assumes the HTML will be rendered in a JS-enabled browser.
- [`fragment`](../../advanced/rendering_js_css#fragment)
- A lightweight HTML fragment to be inserted into a document with AJAX.
- Assumes the page was already rendered with `"document"` strategy.
- No JS / CSS included.
- [`simple`](../../advanced/rendering_js_css#simple)
- Smartly insert JS / CSS into placeholders ([`{% component_js_dependencies %}`](../../../reference/template_tags#component_js_dependencies)) or into `<head>` and `<body>` tags.
- No extra script loaded.
- [`prepend`](../../advanced/rendering_js_css#prepend)
- Insert JS / CSS before the rendered HTML.
- Ignores the placeholders ([`{% component_js_dependencies %}`](../../../reference/template_tags#component_js_dependencies)) and any `<head>`/`<body>` HTML tags.
- No extra script loaded.
- [`append`](../../advanced/rendering_js_css#append)
- Insert JS / CSS after the rendered HTML.
- Ignores the placeholders ([`{% component_js_dependencies %}`](../../../reference/template_tags#component_js_dependencies)) and any `<head>`/`<body>` HTML tags.
- No extra script loaded.
- [`ignore`](../../advanced/rendering_js_css#ignore)
- HTML is left as-is. You can still process it with a different strategy later with
[`render_dependencies()`](../../../reference/api/#django_components.render_dependencies).
- Used for inserting rendered HTML into other components.
!!! info
You can use the `"prepend"` and `"append"` strategies to force to output JS / CSS for components
that don't have neither the placeholders like [`{% component_js_dependencies %}`](../../../reference/template_tags#component_js_dependencies), nor any `<head>`/`<body>` HTML tags:
```py
rendered = Calendar.render_to_response(
request=request,
kwargs={
"date": request.GET.get("date", ""),
},
deps_strategy="append",
)
```
Renders something like this:
```html
<!-- Calendar component -->
<div class="calendar">
...
</div>
<!-- Appended JS / CSS -->
<script src="..."></script>
<link href="..."></link>
```
## Passing context
The [`render()`](../../../reference/api/#django_components.Component.render) and [`render_to_response()`](../../../reference/api/#django_components.Component.render_to_response) methods accept an optional `context` argument.
This sets the context within which the component is rendered.
When a component is rendered within a template with the [`{% component %}`](../../../reference/template_tags#component)
tag, this will be automatically set to the
[Context](https://docs.djangoproject.com/en/5.2/ref/templates/api/#django.template.Context)
instance that is used for rendering the template.
When you call [`Component.render()`](../../../reference/api/#django_components.Component.render) directly from Python,
there is no context object, so you can ignore this input most of the time.
Instead, use `args`, `kwargs`, and `slots` to pass data to the component.
However, you can pass
[`RequestContext`](https://docs.djangoproject.com/en/5.2/ref/templates/api/#django.template.RequestContext)
to the `context` argument, so that the component will gain access to the request object and will use
[context processors](https://docs.djangoproject.com/en/5.2/ref/templates/api/#using-requestcontext).
Read more on [Working with HTTP requests](../http_request).
```py
Button.render(
context=RequestContext(request),
)
```
For advanced use cases, you can use `context` argument to "pre-render" the component in Python, and then
pass the rendered output as plain string to the template. With this, the inner component is rendered as if
it was within the template with [`{% component %}`](../../../reference/template_tags#component).
```py
class Button(Component):
def render(self, context, template):
# Pass `context` to Icon component so it is rendered
# as if nested within Button.
icon = Icon.render(
context=context,
args=["icon-name"],
deps_strategy="ignore",
)
# Update context with icon
with context.update({"icon": icon}):
return template.render(context)
```
!!! warning
Whether the variables defined in `context` are actually available in the template depends on the
[context behavior mode](../../../reference/settings#django_components.app_settings.ComponentsSettings.context_behavior):
- In `"django"` context behavior mode, the template will have access to the keys of this context.
- In `"isolated"` context behavior mode, the template will NOT have access to this context,
and data MUST be passed via component's args and kwargs.
Therefore, it's **strongly recommended** to not rely on defining variables on the context object,
but instead passing them through as `args` and `kwargs`
❌ Don't do this:
```python
html = ProfileCard.render(
context={"name": "John"},
)
```
✅ Do this:
```python
html = ProfileCard.render(
kwargs={"name": "John"},
)
```
## Typing render methods
Neither [`Component.render()`](../../../reference/api/#django_components.Component.render)
nor [`Component.render_to_response()`](../../../reference/api/#django_components.Component.render_to_response)
are typed, due to limitations of Python's type system.
To add type hints, you can wrap the inputs
in component's [`Args`](../../../reference/api/#django_components.Component.Args),
[`Kwargs`](../../../reference/api/#django_components.Component.Kwargs),
and [`Slots`](../../../reference/api/#django_components.Component.Slots) classes.
Read more on [Typing and validation](../../fundamentals/typing_and_validation).
```python
from typing import NamedTuple, Optional
from django_components import Component, Slot, SlotInput
# Define the component with the types
class Button(Component):
class Args(NamedTuple):
name: str
class Kwargs(NamedTuple):
surname: str
age: int
class Slots(NamedTuple):
my_slot: Optional[SlotInput] = None
footer: SlotInput
# Add type hints to the render call
Button.render(
args=Button.Args(
name="John",
),
kwargs=Button.Kwargs(
surname="Doe",
age=30,
),
slots=Button.Slots(
footer=Slot(lambda ctx: "Click me!"),
),
)
```
## Components as input
django_components makes it possible to compose components in a "React-like" way,
where you can render one component and use its output as input to another component:
```python
from django.utils.safestring import mark_safe
# Render the inner component
inner_html = InnerComponent.render(
kwargs={"some_data": "value"},
deps_strategy="ignore", # Important for nesting!
)
# Use inner component's output in the outer component
outer_html = OuterComponent.render(
kwargs={
"content": mark_safe(inner_html), # Mark as safe to prevent escaping
},
)
```
The key here is setting [`deps_strategy="ignore"`](../../advanced/rendering_js_css#ignore) for the inner component. This prevents duplicate
rendering of JS / CSS dependencies when the outer component is rendered.
When `deps_strategy="ignore"`:
- No JS or CSS dependencies will be added to the output HTML
- The component's content is rendered as-is
- The outer component will take care of including all needed dependencies
Read more about [Rendering JS / CSS](../../advanced/rendering_js_css).
## Dynamic components
Django components defines a special "dynamic" component ([`DynamicComponent`](../../../reference/components#django_components.components.dynamic.DynamicComponent)).
Normally, you have to hard-code the component name in the template:
```django
{% component "button" / %}
```
The dynamic component allows you to dynamically render any component based on the `is` kwarg. This is similar
to [Vue's dynamic components](https://vuejs.org/guide/essentials/component-basics#dynamic-components) (`<component :is>`).
```django
{% component "dynamic" is=table_comp data=table_data headers=table_headers %}
{% fill "pagination" %}
{% component "pagination" / %}
{% endfill %}
{% endcomponent %}
```
The args, kwargs, and slot fills are all passed down to the underlying component.
As with other components, the dynamic component can be rendered from Python:
```py
from django_components import DynamicComponent
DynamicComponent.render(
kwargs={
"is": table_comp,
"data": table_data,
"headers": table_headers,
},
slots={
"pagination": PaginationComponent.render(
deps_strategy="ignore",
),
},
)
```
### Dynamic component name
By default, the dynamic component is registered under the name `"dynamic"`. In case of a conflict,
you can set the
[`COMPONENTS.dynamic_component_name`](../../../reference/settings#django_components.app_settings.ComponentsSettings.dynamic_component_name)
setting to change the name used for the dynamic components.
```py
# settings.py
COMPONENTS = ComponentsSettings(
dynamic_component_name="my_dynamic",
)
```
After which you will be able to use the dynamic component with the new name:
```django
{% component "my_dynamic" is=table_comp data=table_data headers=table_headers %}
{% fill "pagination" %}
{% component "pagination" / %}
{% endfill %}
{% endcomponent %}
```
## HTML fragments
Django-components provides a seamless integration with HTML fragments with AJAX ([HTML over the wire](https://hotwired.dev/)),
whether you're using jQuery, HTMX, AlpineJS, vanilla JavaScript, or other.
This is achieved by the combination of the [`"document"`](../../advanced/rendering_js_css#document)
and [`"fragment"`](../../advanced/rendering_js_css#fragment) dependencies rendering strategies.
Read more about [HTML fragments](../../advanced/html_fragments) and [Rendering JS / CSS](../../advanced/rendering_js_css).

View file

@ -1,48 +1,266 @@
As you could have seen in [the tutorial](../../getting_started/adding_js_and_css.md), there's multiple ways how you can associate
HTML / JS / CSS with a component:
## Overview
- You can set [`Component.template`](../../reference/api.md#django_components.Component.template),
[`Component.css`](../../reference/api.md#django_components.Component.css) and
[`Component.js`](../../reference/api.md#django_components.Component.js) to define the main HTML / CSS / JS for a component
as inlined code.
- You can set [`Component.template_file`](../../reference/api.md#django_components.Component.template_file),
[`Component.css_file`](../../reference/api.md#django_components.Component.css_file) and
[`Component.js_file`](../../reference/api.md#django_components.Component.js_file) to define the main HTML / CSS / JS
for a component in separate files.
- You can link additional CSS / JS files using
[`Component.Media.js`](../../reference/api.md#django_components.ComponentMediaInput.js)
and [`Component.Media.css`](../../reference/api.md#django_components.ComponentMediaInput.css).
Each component can define extra or "secondary" CSS / JS files using the nested [`Component.Media`](../../reference/api.md#django_components.Component.Media) class,
by setting [`Component.Media.js`](../../reference/api.md#django_components.ComponentMediaInput.js) and [`Component.Media.css`](../../reference/api.md#django_components.ComponentMediaInput.css).
!!! warning
The [main HTML / JS / CSS files](../html_js_css_files) are limited to 1 per component. This is not the case for the secondary files,
where components can have many of them.
You **cannot** use both inlined code **and** separate file for a single language type:
There is also no special behavior or post-processing for these secondary files, they are loaded as is.
- You can only either set `Component.template` or `Component.template_file`
- You can only either set `Component.css` or `Component.css_file`
- You can only either set `Component.js` or `Component.js_file`
You can use these for third-party libraries, or for shared CSS / JS files.
However, you can freely mix these for different languages:
These must be set as paths, URLs, or [custom objects](#paths-as-objects).
```djc_py
class MyTable(Component):
template: types.django_html = """
<div class="welcome">
Hi there!
</div>
"""
js_file = "my_table.js"
css_file = "my_table.css"
```
```py
@register("calendar")
class Calendar(Component):
class Media:
js = [
"https://unpkg.com/alpinejs@3.14.7/dist/cdn.min.js",
"calendar/script.js",
]
css = [
"https://unpkg.com/tailwindcss@^2/dist/tailwind.min.css",
"calendar/style.css",
]
```
!!! note
django-component's management of files is inspired by [Django's `Media` class](https://docs.djangoproject.com/en/5.0/topics/forms/media/).
django-component's management of files is inspired by [Django's `Media` class](https://docs.djangoproject.com/en/5.2/topics/forms/media/).
To be familiar with how Django handles static files, we recommend reading also:
- [How to manage static files (e.g. images, JavaScript, CSS)](https://docs.djangoproject.com/en/5.0/howto/static-files/)
- [How to manage static files (e.g. images, JavaScript, CSS)](https://docs.djangoproject.com/en/5.2/howto/static-files/)
## Defining file paths relative to component
## `Media` class
<!-- TODO: This section deserves to be expanded with more examples,
so it's easier to follow. Right now it assumes that the read
is familiar with Django's Media class, as we describe our Media class
in constrast to it.
Instead we should go over all features / behaviours of the `Media` class.
We should also make `Media` class into a separate extension,
and then have a separate page on "Secondary JS / CSS files".
-->
Use the `Media` class to define secondary JS / CSS files for a component.
This `Media` class behaves similarly to
[Django's Media class](https://docs.djangoproject.com/en/5.2/topics/forms/media/#assets-as-a-static-definition):
- **Static paths** - Paths are handled as static file paths, and are resolved to URLs with Django's
[`{% static %}`](https://docs.djangoproject.com/en/5.2/ref/templates/builtins/#static) template tag.
- **URLs** - A path that starts with `http`, `https`, or `/` is considered a URL. URLs are NOT resolved with [`{% static %}`](https://docs.djangoproject.com/en/5.2/ref/templates/builtins/#static).
- **HTML tags** - Both static paths and URLs are rendered to `<script>` and `<link>` HTML tags with
`media_class.render_js()` and `media_class.render_css()`.
- **Bypass formatting** - A [`SafeString`](https://docs.djangoproject.com/en/5.2/ref/utils/#django.utils.safestring.SafeString),
or a function (with `__html__` method) is considered an already-formatted HTML tag, skipping both static file
resolution and rendering with `media_class.render_js()` or `media_class.render_css()`.
- **Inheritance** - You can set [`extend`](../../../reference/api#django_components.ComponentMediaInput.extend) to configure
whether to inherit JS / CSS from parent components. See [Media inheritance](#media-inheritance).
However, there's a few differences from Django's Media class:
1. Our Media class accepts various formats for the JS and CSS files: either a single file, a list,
or (CSS-only) a dictonary (See [`ComponentMediaInput`](../../../reference/api#django_components.ComponentMediaInput)).
2. Individual JS / CSS files can be any of `str`, `bytes`, `Path`,
[`SafeString`](https://docs.djangoproject.com/en/5.2/ref/utils/#django.utils.safestring.SafeString), or a function
(See [`ComponentMediaInputPath`](../../../reference/api#django_components.ComponentMediaInputPath)).
3. Individual JS / CSS files can be glob patterns, e.g. `*.js` or `styles/**/*.css`.
4. If you set [`Media.extend`](../../../reference/api/#django_components.ComponentMediaInput.extend) to a list,
it should be a list of [`Component`](../../../reference/api/#django_components.Component) classes.
```py
from components.layout import LayoutComp
class MyTable(Component):
class Media:
js = [
"path/to/script.js",
"path/to/*.js", # Or as a glob
"https://unpkg.com/alpinejs@3.14.7/dist/cdn.min.js", # AlpineJS
]
css = {
"all": [
"path/to/style.css",
"path/to/*.css", # Or as a glob
"https://unpkg.com/tailwindcss@^2/dist/tailwind.min.css", # TailwindCSS
],
"print": ["path/to/style2.css"],
}
# Reuse JS / CSS from LayoutComp
extend = [
LayoutComp,
]
```
### CSS media types
You can define which stylesheets will be associated with which
[CSS media types](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_media_queries/Using_media_queries#targeting_media_types). You do so by defining CSS files as a dictionary.
See the corresponding [Django Documentation](https://docs.djangoproject.com/en/5.2/topics/forms/media/#css).
Again, you can set either a single file or a list of files per media type:
```py
class MyComponent(Component):
class Media:
css = {
"all": "path/to/style1.css",
"print": ["path/to/style2.css", "path/to/style3.css"],
}
```
Which will render the following HTML:
```html
<link href="/static/path/to/style1.css" media="all" rel="stylesheet">
<link href="/static/path/to/style2.css" media="print" rel="stylesheet">
<link href="/static/path/to/style3.css" media="print" rel="stylesheet">
```
!!! note
When you define CSS as a string or a list, the `all` media type is implied.
So these two examples are the same:
```py
class MyComponent(Component):
class Media:
css = "path/to/style1.css"
```
```py
class MyComponent(Component):
class Media:
css = {
"all": ["path/to/style1.css"],
}
```
### Media inheritance
By default, the media files are inherited from the parent component.
```python
class ParentComponent(Component):
class Media:
js = ["parent.js"]
class MyComponent(ParentComponent):
class Media:
js = ["script.js"]
print(MyComponent.media._js) # ["parent.js", "script.js"]
```
You can set the component NOT to inherit from the parent component by setting the [`extend`](../../reference/api.md#django_components.ComponentMediaInput.extend) attribute to `False`:
```python
class ParentComponent(Component):
class Media:
js = ["parent.js"]
class MyComponent(ParentComponent):
class Media:
extend = False # Don't inherit parent media
js = ["script.js"]
print(MyComponent.media._js) # ["script.js"]
```
Alternatively, you can specify which components to inherit from. In such case, the media files are inherited ONLY from the specified components, and NOT from the original parent components:
```python
class ParentComponent(Component):
class Media:
js = ["parent.js"]
class MyComponent(ParentComponent):
class Media:
# Only inherit from these, ignoring the files from the parent
extend = [OtherComponent1, OtherComponent2]
js = ["script.js"]
print(MyComponent.media._js) # ["script.js", "other1.js", "other2.js"]
```
!!! info
The `extend` behaves consistently with
[Django's Media class](https://docs.djangoproject.com/en/5.2/topics/forms/media/#extend),
with one exception:
- When you set `extend` to a list, the list is expected to contain Component classes (or other classes that have a nested `Media` class).
### Accessing Media files
To access the files that you defined under [`Component.Media`](../../../reference/api#django_components.Component.Media),
use [`Component.media`](../../reference/api.md#django_components.Component.media) (lowercase).
This is consistent behavior with
[Django's Media class](https://docs.djangoproject.com/en/5.2/topics/forms/media/#assets-as-a-static-definition).
```py
class MyComponent(Component):
class Media:
js = "path/to/script.js"
css = "path/to/style.css"
print(MyComponent.media)
# Output:
# <script src="/static/path/to/script.js"></script>
# <link href="/static/path/to/style.css" media="all" rel="stylesheet">
```
When working with component media files, it is important to understand the difference:
- `Component.Media`
- Is the "raw" media definition, or the input, which holds only the component's **own** media definition
- This class is NOT instantiated, it merely holds the JS / CSS files.
- `Component.media`
- Returns all resolved media files, **including** those inherited from parent components
- Is an instance of [`Component.media_class`](../../reference/api.md#django_components.Component.media_class)
```python
class ParentComponent(Component):
class Media:
js = ["parent.js"]
class ChildComponent(ParentComponent):
class Media:
js = ["child.js"]
# Access only this component's media
print(ChildComponent.Media.js) # ["child.js"]
# Access all inherited media
print(ChildComponent.media._js) # ["parent.js", "child.js"]
```
!!! note
You should **not** manually modify `Component.media` or `Component.Media` after the component has been resolved, as this may lead to unexpected behavior.
If you want to modify the class that is instantiated for [`Component.media`](../../reference/api.md#django_components.Component.media),
you can configure [`Component.media_class`](../../reference/api.md#django_components.Component.media_class)
([See example](#rendering-paths)).
## File paths
Unlike the [main HTML / JS / CSS files](../html_js_css_files), the path definition for the secondary files are quite ergonomic.
### Relative to component
As seen in the [getting started example](../../getting_started/your_first_component.md), to associate HTML / JS / CSS
files with a component, you can set them as
@ -117,93 +335,30 @@ class Calendar(Component):
NOTE: In case of ambiguity, the preference goes to resolving the files relative to the component's directory.
## Defining additional JS and CSS files
### Globs
Each component can have only a single template, and single main JS and CSS. However, you can define additional JS or CSS
using the nested [`Component.Media` class](../../../reference/api#django_components.Component.Media).
Components can have many secondary files. To simplify their declaration, you can use globs.
This `Media` class behaves similarly to
[Django's Media class](https://docs.djangoproject.com/en/5.1/topics/forms/media/#assets-as-a-static-definition):
Globs MUST be relative to the component's directory.
- Paths are generally handled as static file paths, and resolved URLs are rendered to HTML with
`media_class.render_js()` or `media_class.render_css()`.
- A path that starts with `http`, `https`, or `/` is considered a URL, skipping the static file resolution.
This path is still rendered to HTML with `media_class.render_js()` or `media_class.render_css()`.
- A [`SafeString`](https://docs.djangoproject.com/en/5.1/ref/utils/#django.utils.safestring.SafeString),
or a function (with `__html__` method) is considered an already-formatted HTML tag, skipping both static file
resolution and rendering with `media_class.render_js()` or `media_class.render_css()`.
- You can set [`extend`](../../../reference/api#django_components.ComponentMediaInput.extend) to configure
whether to inherit JS / CSS from parent components. See
[Controlling Media Inheritance](../../fundamentals/defining_js_css_html_files/#controlling-media-inheritance).
```py title="[project root]/components/calendar/calendar.py"
from django_components import Component, register
However, there's a few differences from Django's Media class:
1. Our Media class accepts various formats for the JS and CSS files: either a single file, a list,
or (CSS-only) a dictonary (See [`ComponentMediaInput`](../../../reference/api#django_components.ComponentMediaInput)).
2. Individual JS / CSS files can be any of `str`, `bytes`, `Path`,
[`SafeString`](https://docs.djangoproject.com/en/5.1/ref/utils/#django.utils.safestring.SafeString), or a function
(See [`ComponentMediaInputPath`](../../../reference/api#django_components.ComponentMediaInputPath)).
3. Individual JS / CSS files can be glob patterns, e.g. `*.js` or `styles/**/*.css`.
4. If you set [`Media.extend`](../../../reference/api/#django_components.ComponentMediaInput.extend) to a list,
it should be a list of [`Component`](../../../reference/api/#django_components.Component) classes.
```py
class MyTable(Component):
@register("calendar")
class Calendar(Component):
class Media:
js = [
"path/to/script.js",
"path/to/*.js", # Or as a glob
"https://unpkg.com/alpinejs@3.14.7/dist/cdn.min.js", # AlpineJS
"path/to/*.js",
"another/path/*.js",
]
css = {
"all": [
"path/to/style.css",
"path/to/*.css", # Or as a glob
"https://unpkg.com/tailwindcss@^2/dist/tailwind.min.css", # TailwindCSS
],
"print": ["path/to/style2.css"],
}
css = "*.css"
```
## Configuring CSS Media Types
How this works is that django-components will detect that the path is a glob, and will try to resolve all files matching the glob pattern relative to the component's directory.
You can define which stylesheets will be associated with which
[CSS Media types](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_media_queries/Using_media_queries#targeting_media_types). You do so by defining CSS files as a dictionary.
After that, the file paths are handled the same way as if you defined them explicitly.
See the corresponding [Django Documentation](https://docs.djangoproject.com/en/5.0/topics/forms/media/#css).
Again, you can set either a single file or a list of files per media type:
```py
class MyComponent(Component):
class Media:
css = {
"all": "path/to/style1.css",
"print": ["path/to/style2.css", "path/to/style3.css"],
}
```
!!! note
When you define CSS as a string or a list, the `all` media type is implied.
So these two examples are the same:
```py
class MyComponent(Component):
class Media:
css = "path/to/style1.css"
```
```py
class MyComponent(Component):
class Media:
css = {
"all": ["path/to/style1.css"],
}
```
## Supported types for file paths
### Supported types
File paths can be any of:
@ -213,7 +368,7 @@ File paths can be any of:
- `SafeData` (`__html__` method)
- `Callable` that returns any of the above, evaluated at class creation (`__new__`)
See [`ComponentMediaInputPath`](../../../reference/api#django_components.ComponentMediaInputPath).
To help with typing the union, use [`ComponentMediaInputPath`](../../../reference/api#django_components.ComponentMediaInputPath).
```py
from pathlib import Path
@ -238,18 +393,18 @@ class SimpleComponent(Component):
]
```
## Paths as objects
### Paths as objects
In the example [above](#supported-types-for-file-paths), you can see that when we used Django's
[`mark_safe()`](https://docs.djangoproject.com/en/5.1/ref/utils/#django.utils.safestring.mark_safe)
to mark a string as a [`SafeString`](https://docs.djangoproject.com/en/5.1/ref/utils/#django.utils.safestring.SafeString),
we had to define the full `<script>`/`<link>` tag.
In the example [above](#supported-types), you can see that when we used Django's
[`mark_safe()`](https://docs.djangoproject.com/en/5.2/ref/utils/#django.utils.safestring.mark_safe)
to mark a string as a [`SafeString`](https://docs.djangoproject.com/en/5.2/ref/utils/#django.utils.safestring.SafeString),
we had to define the URL / path as an HTML `<script>`/`<link>` elements.
This is an extension of Django's [Paths as objects](https://docs.djangoproject.com/en/5.0/topics/forms/media/#paths-as-objects)
feature, where "safe" strings are taken as is, and accessed only at render time.
This is an extension of Django's [Paths as objects](https://docs.djangoproject.com/en/5.2/topics/forms/media/#paths-as-objects)
feature, where "safe" strings are taken as is, and are accessed only at render time.
Because of that, the paths defined as "safe" strings are NEVER resolved, neither relative to component's directory,
nor relative to [`COMPONENTS.dirs`](../../reference/settings.md#django_components.app_settings.ComponentsSettings.dirs).
nor relative to [`COMPONENTS.dirs`](../../reference/settings.md#django_components.app_settings.ComponentsSettings.dirs). It is assumed that you will define the full `<script>`/`<link>` tag with the correct URL / path.
"Safe" strings can be used to lazily resolve a path, or to customize the `<script>` or `<link>` tag for individual paths:
@ -257,10 +412,12 @@ In the example below, we make use of "safe" strings to add `type="module"` to th
In this case, we implemented a "safe" string by defining a `__html__` method.
```py
# Path object
class ModuleJsPath:
def __init__(self, static_path: str) -> None:
self.static_path = static_path
# Lazily resolve the path
def __html__(self):
full_path = static(self.static_path)
return format_html(
@ -271,11 +428,6 @@ class ModuleJsPath:
class Calendar(Component):
template_file = "calendar/template.html"
def get_context_data(self, date):
return {
"date": date,
}
class Media:
css = "calendar/style1.css"
js = [
@ -286,13 +438,17 @@ class Calendar(Component):
]
```
## Customize how paths are rendered into HTML tags
### Rendering paths
Sometimes you may need to change how all CSS `<link>` or JS `<script>` tags are rendered for a given component. You can achieve this by providing your own subclass of [Django's `Media` class](https://docs.djangoproject.com/en/5.0/topics/forms/media) to component's `media_class` attribute.
As part of the rendering process, the secondary JS / CSS files are resolved and rendered into `<link>` and `<script>` HTML tags, so they can be inserted into the render.
Normally, the JS and CSS paths are passed to `Media` class, which decides how the paths are resolved and how the `<link>` and `<script>` tags are constructed.
In the [Paths as objects](#paths-as-objects) section, we saw that we can use that to selectively change
how the HTML tags are constructed.
To change how the tags are constructed, you can override the [`Media.render_js` and `Media.render_css` methods](https://github.com/django/django/blob/fa7848146738a9fe1d415ee4808664e54739eeb7/django/forms/widgets.py#L102):
However, if you need to change how ALL CSS and JS files are rendered for a given component,
you can provide your own subclass of [Django's `Media` class](https://docs.djangoproject.com/en/5.2/topics/forms/media) to the [`Component.media_class`](../../reference/api.md#django_components.Component.media_class) attribute.
To change how the tags are constructed, you can override the [`Media.render_js()` and `Media.render_css()` methods](https://github.com/django/django/blob/fa7848146738a9fe1d415ee4808664e54739eeb7/django/forms/widgets.py#L102):
```py
from django.forms.widgets import Media
@ -326,189 +482,3 @@ class Calendar(Component):
# Override the behavior of Media class
media_class = MyMedia
```
## Accessing component's HTML / JS / CSS
Component's HTML / CSS / JS is resolved and loaded lazily.
This means that, when you specify any of
[`template_file`](../../reference/api.md#django_components.Component.template_file),
[`js_file`](../../reference/api.md#django_components.Component.js_file),
[`css_file`](../../reference/api.md#django_components.Component.css_file),
or [`Media.js/css`](../../reference/api.md#django_components.Component.Media),
these file paths will be resolved only once you either:
1. Access any of the following attributes on the component:
- [`media`](../../reference/api.md#django_components.Component.media),
[`template`](../../reference/api.md#django_components.Component.template),
[`template_file`](../../reference/api.md#django_components.Component.template_file),
[`js`](../../reference/api.md#django_components.Component.js),
[`js_file`](../../reference/api.md#django_components.Component.js_file),
[`css`](../../reference/api.md#django_components.Component.css),
[`css_file`](../../reference/api.md#django_components.Component.css_file)
2. Render the component.
Once the component's media files have been loaded once, they will remain in-memory
on the Component class:
- HTML from [`Component.template_file`](../../reference/api.md#django_components.Component.template_file)
will be available under [`Component.template`](../../reference/api.md#django_components.Component.template)
- CSS from [`Component.css_file`](../../reference/api.md#django_components.Component.css_file)
will be available under [`Component.css`](../../reference/api.md#django_components.Component.css)
- JS from [`Component.js_file`](../../reference/api.md#django_components.Component.js_file)
will be available under [`Component.js`](../../reference/api.md#django_components.Component.js)
Thus, whether you define HTML via
[`Component.template_file`](../../reference/api.md#django_components.Component.template_file)
or [`Component.template`](../../reference/api.md#django_components.Component.template),
you can always access the HTML content under [`Component.template`](../../reference/api.md#django_components.Component.template).
And the same applies for JS and CSS.
**Example:**
```py
# When we create Calendar component, the files like `calendar/template.html`
# are not yet loaded!
@register("calendar")
class Calendar(Component):
template_file = "calendar/template.html"
css_file = "calendar/style.css"
js_file = "calendar/script.js"
class Media:
css = "calendar/style1.css"
js = "calendar/script2.js"
# It's only at this moment that django-components reads the files like `calendar/template.html`
print(Calendar.css)
# Output:
# .calendar {
# width: 200px;
# background: pink;
# }
```
!!! warning
**Do NOT modify HTML / CSS / JS after it has been loaded**
django-components assumes that the component's media files like `js_file` or `Media.js/css` are static.
If you need to dynamically change these media files, consider instead defining multiple Components.
Modifying these files AFTER the component has been loaded at best does nothing. However, this is
an untested behavior, which may lead to unexpected errors.
## Accessing component's Media files
To access the files that you defined under [`Component.Media`](../../../reference/api#django_components.Component.Media),
use [`Component.media`](../../reference/api.md#django_components.Component.media) (lowercase).
This is consistent behavior with
[Django's Media class](https://docs.djangoproject.com/en/5.1/topics/forms/media/#assets-as-a-static-definition).
```py
class MyComponent(Component):
class Media:
js = "path/to/script.js"
css = "path/to/style.css"
print(MyComponent.media)
# Output:
# <script src="/static/path/to/script.js"></script>
# <link href="/static/path/to/style.css" media="all" rel="stylesheet">
```
### `Component.Media` vs `Component.media`
When working with component media files, there are a few important concepts to understand:
- `Component.Media`
- Is the "raw" media definition, or the input, which holds only the component's **own** media definition
- This class is NOT instantiated, it merely holds the JS / CSS files.
- `Component.media`
- Returns all resolved media files, **including** those inherited from parent components
- Is an instance of [`Component.media_class`](../../reference/api.md#django_components.Component.media_class)
```python
class ParentComponent(Component):
class Media:
js = ["parent.js"]
class ChildComponent(ParentComponent):
class Media:
js = ["child.js"]
# Access only this component's media
print(ChildComponent.Media.js) # ["child.js"]
# Access all inherited media
print(ChildComponent.media._js) # ["parent.js", "child.js"]
```
!!! note
You should **not** manually modify `Component.media` or `Component.Media` after the component has been resolved, as this may lead to unexpected behavior.
If you want to modify the class that is instantiated for [`Component.media`](../../reference/api.md#django_components.Component.media),
you can configure [`Component.media_class`](../../reference/api.md#django_components.Component.media_class)
([See example](#customize-how-paths-are-rendered-into-html-tags)).
## Controlling Media Inheritance
By default, the media files are inherited from the parent component.
```python
class ParentComponent(Component):
class Media:
js = ["parent.js"]
class MyComponent(ParentComponent):
class Media:
js = ["script.js"]
print(MyComponent.media._js) # ["parent.js", "script.js"]
```
You can set the component NOT to inherit from the parent component by setting the [`extend`](../../reference/api.md#django_components.ComponentMediaInput.extend) attribute to `False`:
```python
class ParentComponent(Component):
class Media:
js = ["parent.js"]
class MyComponent(ParentComponent):
class Media:
extend = False # Don't inherit parent media
js = ["script.js"]
print(MyComponent.media._js) # ["script.js"]
```
Alternatively, you can specify which components to inherit from. In such case, the media files are inherited ONLY from the specified components, and NOT from the original parent components:
```python
class ParentComponent(Component):
class Media:
js = ["parent.js"]
class MyComponent(ParentComponent):
class Media:
# Only inherit from these, ignoring the files from the parent
extend = [OtherComponent1, OtherComponent2]
js = ["script.js"]
print(MyComponent.media._js) # ["script.js", "other1.js", "other2.js"]
```
!!! info
The `extend` behaves consistently with
[Django's Media class](https://docs.djangoproject.com/en/5.1/topics/forms/media/#extend),
with one exception:
- When you set `extend` to a list, the list is expected to contain Component classes (or other classes that have a nested `Media` class).

View file

@ -1,17 +1,38 @@
Components can be defined in a single file, which is useful for small components. To do this, you can use the `template`, `js`, and `css` class attributes instead of the `template_file`, `js_file`, and `css_file`.
Components can be defined in a single file, inlining the HTML, JS and CSS within the Python code.
## Writing single file components
To do this, you can use the
[`template`](../../../reference/api#django_components.Component.template),
[`js`](../../../reference/api#django_components.Component.js),
and [`css`](../../../reference/api#django_components.Component.css)
class attributes instead of the
[`template_file`](../../../reference/api#django_components.Component.template_file),
[`js_file`](../../../reference/api#django_components.Component.js_file),
and [`css_file`](../../../reference/api#django_components.Component.css_file).
For example, here's the calendar component from
the [Getting started](../../getting_started/your_first_component.md) tutorial,
defined in a single file:
the [Getting started](../../getting_started/your_first_component.md) tutorial:
```py title="calendar.py"
from django_components import Component
class Calendar(Component):
template_file = "calendar.html"
js_file = "calendar.js"
css_file = "calendar.css"
```
And here is the same component, rewritten in a single file:
```djc_py title="[project root]/components/calendar.py"
from django_components import Component, register, types
@register("calendar")
class Calendar(Component):
def get_context_data(self, date):
def get_template_data(self, args, kwargs, slots, context):
return {
"date": date,
"date": kwargs["date"],
}
template: types.django_html = """
@ -41,6 +62,194 @@ class Calendar(Component):
"""
```
This makes it easy to create small components without having to create a separate template, CSS, and JS file.
You can mix and match, so you can have a component with inlined HTML,
while the JS and CSS are in separate files:
To add syntax highlighting to these snippets, head over to [Syntax highlighting](../../guides/setup/syntax_highlight.md).
```djc_py title="[project root]/components/calendar.py"
from django_components import Component, register, types
@register("calendar")
class Calendar(Component):
js_file = "calendar.js"
css_file = "calendar.css"
template: types.django_html = """
<div class="calendar">
Today's date is <span>{{ date }}</span>
</div>
"""
```
## Syntax highlighting
If you "inline" the HTML, JS and CSS code into the Python class, you should set up
syntax highlighting to let your code editor know that the inlined code is HTML, JS and CSS.
In the examples above, we've annotated the
[`template`](../../../reference/api#django_components.Component.template),
[`js`](../../../reference/api#django_components.Component.js),
and [`css`](../../../reference/api#django_components.Component.css)
attributes with
the `types.django_html`, `types.js` and `types.css` types. These are used for syntax highlighting in VSCode.
!!! warning
Autocompletion / intellisense does not work in the inlined code.
Help us add support for intellisense in the inlined code! Start a conversation in the
[GitHub Discussions](https://github.com/django-components/django-components/discussions).
### VSCode
1. First install [Python Inline Source Syntax Highlighting](https://marketplace.visualstudio.com/items?itemName=samwillis.python-inline-source) extension, it will give you syntax highlighting for the template, CSS, and JS.
2. Next, in your component, set typings of
[`Component.template`](../../../reference/api#django_components.Component.template),
[`Component.js`](../../../reference/api#django_components.Component.js),
[`Component.css`](../../../reference/api#django_components.Component.css)
to `types.django_html`, `types.css`, and `types.js` respectively. The extension will recognize these and will activate syntax highlighting.
```djc_py title="[project root]/components/calendar.py"
from django_components import Component, register, types
@register("calendar")
class Calendar(Component):
def get_template_data(self, args, kwargs, slots, context):
return {
"date": kwargs["date"],
}
template: types.django_html = """
<div class="calendar-component">
Today's date is <span>{{ date }}</span>
</div>
"""
css: types.css = """
.calendar-component {
width: 200px;
background: pink;
}
.calendar-component span {
font-weight: bold;
}
"""
js: types.js = """
(function(){
if (document.querySelector(".calendar-component")) {
document.querySelector(".calendar-component").onclick = function(){ alert("Clicked calendar!"); };
}
})()
"""
```
### Pycharm (or other Jetbrains IDEs)
With PyCharm (or any other editor from Jetbrains), you don't need to use `types.django_html`, `types.css`, `types.js` since Pycharm uses [language injections](https://www.jetbrains.com/help/pycharm/using-language-injections.html).
You only need to write the comments `# language=<lang>` above the variables.
```djc_py
from django_components import Component, register
@register("calendar")
class Calendar(Component):
def get_template_data(self, args, kwargs, slots, context):
return {
"date": kwargs["date"],
}
# language=HTML
template= """
<div class="calendar-component">
Today's date is <span>{{ date }}</span>
</div>
"""
# language=CSS
css = """
.calendar-component {
width: 200px;
background: pink;
}
.calendar-component span {
font-weight: bold;
}
"""
# language=JS
js = """
(function(){
if (document.querySelector(".calendar-component")) {
document.querySelector(".calendar-component").onclick = function(){ alert("Clicked calendar!"); };
}
})()
"""
```
### Markdown code blocks with Pygments
[Pygments](https://pygments.org/) is a syntax highlighting library written in Python. It's also what's used by this documentation site ([mkdocs-material](https://squidfunk.github.io/mkdocs-material/)) to highlight code blocks.
To write code blocks with syntax highlighting, you need to install the [`pygments-djc`](https://pypi.org/project/pygments-djc/) package.
```bash
pip install pygments-djc
```
And then initialize it by importing `pygments_djc` somewhere in your project:
```python
import pygments_djc
```
Now you can use the `djc_py` code block to write code blocks with syntax highlighting for components.
```txt
\```djc_py
from django_components import Component, register
@register("calendar")
class Calendar(Component):
template = """
<div class="calendar-component">
Today's date is <span>{{ date }}</span>
</div>
"""
css = """
.calendar-component {
width: 200px;
background: pink;
}
.calendar-component span {
font-weight: bold;
}
"""
\```
```
Will be rendered as below. Notice that the CSS and HTML are highlighted correctly:
```djc_py
from django_components import Component, register
@register("calendar")
class Calendar(Component):
template= """
<div class="calendar-component">
Today's date is <span>{{ date }}</span>
</div>
"""
css = """
.calendar-component {
width: 200px;
background: pink;
}
.calendar-component span {
font-weight: bold;
}
"""
```

File diff suppressed because it is too large Load diff

View file

@ -4,7 +4,7 @@ In such cases, you can extract shared behavior into a standalone component class
When subclassing a component, there's a couple of things to keep in mind:
### Template, JS, and CSS Inheritance
## Template, JS, and CSS inheritance
When it comes to the pairs:
@ -52,13 +52,13 @@ class CustomCard(BaseCard):
"""
```
### Media Class Inheritance
## Media inheritance
The [`Component.Media`](../../reference/api.md#django_components.Component.Media) nested class follows Django's media inheritance rules:
- If both parent and child define a `Media` class, the child's media will automatically include both its own and the parent's JS and CSS files.
- This behavior can be configured using the [`extend`](../../reference/api.md#django_components.Component.Media.extend) attribute in the Media class, similar to Django's forms.
Read more on this in [Controlling Media Inheritance](./defining_js_css_html_files.md#controlling-media-inheritance).
Read more on this in [Media inheritance](./secondary_js_css_files/#media-inheritance).
For example:
@ -83,7 +83,35 @@ class SimpleModal(BaseModal):
js = ["simple_modal.js"] # Only this JS will be included
```
### Regular Python Inheritance
## Opt out of inheritance
For the following media attributes, when you don't want to inherit from the parent,
but you also don't need to set the template / JS / CSS to any specific value,
you can set these attributes to `None`.
- [`template`](../../reference/api.md#django_components.Component.template) / [`template_file`](../../reference/api.md#django_components.Component.template_file)
- [`js`](../../reference/api.md#django_components.Component.js) / [`js_file`](../../reference/api.md#django_components.Component.js_file)
- [`css`](../../reference/api.md#django_components.Component.css) / [`css_file`](../../reference/api.md#django_components.Component.css_file)
- [`Media`](../../reference/api.md#django_components.Component.Media) class
For example:
```djc_py
class BaseForm(Component):
template = "..."
css = "..."
js = "..."
class Media:
js = ["form.js"]
# Use parent's template and CSS, but no JS
class ContactForm(BaseForm):
js = None
Media = None
```
## Regular Python inheritance
All other attributes and methods (including the [`Component.View`](../../reference/api.md#django_components.ComponentView) class and its methods) follow standard Python inheritance rules.
@ -100,7 +128,7 @@ class BaseForm(Component):
</form>
"""
def get_context_data(self, **kwargs):
def get_template_data(self, args, kwargs, slots, context):
return {
"form_content": self.get_form_content(),
"submit_text": "Submit"
@ -112,8 +140,8 @@ class BaseForm(Component):
class ContactForm(BaseForm):
# Extend parent's "context"
# but override "submit_text"
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
def get_template_data(self, args, kwargs, slots, context):
context = super().get_template_data(args, kwargs, slots, context)
context["submit_text"] = "Send Message"
return context

View file

@ -30,14 +30,12 @@ so are still valid:
</body>
```
These can then be accessed inside `get_context_data` so:
These can then be accessed inside `get_template_data` so:
```py
@register("calendar")
class Calendar(Component):
# Since # . @ - are not valid identifiers, we have to
# use `**kwargs` so the method can accept these args.
def get_context_data(self, **kwargs):
def get_template_data(self, args, kwargs, slots, context):
return {
"date": kwargs["my-date"],
"id": kwargs["#some_id"],
@ -85,24 +83,24 @@ Other than that, you can use spread operators multiple times, and even put keywo
In a case of conflicts, the values added later (right-most) overwrite previous values.
## Use template tags inside component inputs
## Template tags inside literal strings
_New in version 0.93_
When passing data around, sometimes you may need to do light transformations, like negating booleans or filtering lists.
Normally, what you would have to do is to define ALL the variables
inside `get_context_data()`. But this can get messy if your components contain a lot of logic.
inside `get_template_data()`. But this can get messy if your components contain a lot of logic.
```py
@register("calendar")
class Calendar(Component):
def get_context_data(self, id: str, editable: bool):
def get_template_data(self, args, kwargs, slots, context):
return {
"editable": editable,
"readonly": not editable,
"input_id": f"input-{id}",
"icon_id": f"icon-{id}",
"editable": kwargs["editable"],
"readonly": not kwargs["editable"],
"input_id": f"input-{kwargs['id']}",
"icon_id": f"icon-{kwargs['id']}",
...
}
```
@ -119,26 +117,22 @@ Instead, template tags in django_components (`{% component %}`, `{% slot %}`, `{
/ %}
```
In the example above:
In the example above, the component receives:
- Component `test` receives a positional argument with value `"As positional arg "`. The comment is omitted.
- Kwarg `title` is passed as a string, e.g. `John Doe`
- Kwarg `id` is passed as `int`, e.g. `15`
- Kwarg `readonly` is passed as `bool`, e.g. `False`
- Kwarg `author` is passed as a string, e.g. `John Wick ` (Comment omitted)
- Positional argument `"As positional arg "` (Comment omitted)
- `title` - passed as `str`, e.g. `John Doe`
- `id` - passed as `int`, e.g. `15`
- `readonly` - passed as `bool`, e.g. `False`
- `author` - passed as `str`, e.g. `John Wick ` (Comment omitted)
This is inspired by [django-cotton](https://github.com/wrabit/django-cotton#template-expressions-in-attributes).
### Passing data as string vs original values
Sometimes you may want to use the template tags to transform
or generate the data that is then passed to the component.
In the example above, the kwarg `id` was passed as an integer, NOT a string.
The data doesn't necessarily have to be strings. In the example above, the kwarg `id` was passed as an integer, NOT a string.
Although the string literals for components inputs are treated as regular Django templates, there is one special case:
When the string literal contains only a single template tag, with no extra text, then the value is passed as the original type instead of a string.
When the string literal contains only a single template tag, with no extra text (and no extra whitespace),
then the value is passed as the original type instead of a string.
Here, `page` is an integer:
@ -204,10 +198,10 @@ class MyComp(Component):
{% component "other" attrs=attrs / %}
"""
def get_context_data(self, some_id: str):
def get_template_data(self, args, kwargs, slots, context):
attrs = {
"class": "pa-4 flex",
"data-some-id": some_id,
"data-some-id": kwargs["some_id"],
"@click.stop": "onClickHandler",
}
return {"attrs": attrs}
@ -235,8 +229,8 @@ class MyComp(Component):
/ %}
"""
def get_context_data(self, some_id: str):
return {"some_id": some_id}
def get_template_data(self, args, kwargs, slots, context):
return {"some_id": kwargs["some_id"]}
```
Sweet! Now all the relevant HTML is inside the template, and we can move it to a separate file with confidence:

View file

@ -0,0 +1,668 @@
## Typing overview
<!-- TODO_V1 - REMOVE IN v1 -->
!!! warning
In versions 0.92 to 0.139 (inclusive), the component typing was specified through generics.
Since v0.140, the types must be specified as class attributes of the Component class - `Args`, `Kwargs`, `Slots`, `TemplateData`, `JsData`, and `CssData`.
See [Migrating from generics to class attributes](#migrating-from-generics-to-class-attributes) for more info.
!!! warning
Input validation was NOT part of Django Components between versions 0.136 and 0.139 (inclusive).
The [`Component`](../../../reference/api#django_components.Component) class optionally accepts class attributes
that allow you to define the types of args, kwargs, slots, as well as the data returned from the data methods.
Use this to add type hints to your components, to validate the inputs at runtime, and to document them.
```py
from typing import NamedTuple, Optional
from django.template import Context
from django_components import Component, SlotInput
class Button(Component):
class Args(NamedTuple):
size: int
text: str
class Kwargs(NamedTuple):
variable: str
maybe_var: Optional[int] = None # May be omitted
class Slots(NamedTuple):
my_slot: Optional[SlotInput] = None
def get_template_data(self, args: Args, kwargs: Kwargs, slots: Slots, context: Context):
...
template_file = "button.html"
```
The class attributes are:
- [`Args`](../../../reference/api#django_components.Component.Args) - Type for positional arguments.
- [`Kwargs`](../../../reference/api#django_components.Component.Kwargs) - Type for keyword arguments.
- [`Slots`](../../../reference/api#django_components.Component.Slots) - Type for slots.
- [`TemplateData`](../../../reference/api#django_components.Component.TemplateData) - Type for data returned from [`get_template_data()`](../../../reference/api#django_components.Component.get_template_data).
- [`JsData`](../../../reference/api#django_components.Component.JsData) - Type for data returned from [`get_js_data()`](../../../reference/api#django_components.Component.get_js_data).
- [`CssData`](../../../reference/api#django_components.Component.CssData) - Type for data returned from [`get_css_data()`](../../../reference/api#django_components.Component.get_css_data).
You can specify as many or as few of these as you want, the rest will default to `None`.
## Typing inputs
You can use [`Component.Args`](../../../reference/api#django_components.Component.Args),
[`Component.Kwargs`](../../../reference/api#django_components.Component.Kwargs),
and [`Component.Slots`](../../../reference/api#django_components.Component.Slots) to type the component inputs.
When you set these classes, at render time the `args`, `kwargs`, and `slots` parameters of the data methods
([`get_template_data()`](../../../reference/api#django_components.Component.get_template_data),
[`get_js_data()`](../../../reference/api#django_components.Component.get_js_data),
[`get_css_data()`](../../../reference/api#django_components.Component.get_css_data))
will be instances of these classes.
This way, each component can have runtime validation of the inputs:
- When you use [`NamedTuple`](https://docs.python.org/3/library/typing.html#typing.NamedTuple)
or [`@dataclass`](https://docs.python.org/3/library/dataclasses.html#dataclasses.dataclass),
instantiating these classes will check ONLY for the presence of the attributes.
- When you use [Pydantic models](https://docs.pydantic.dev/latest/concepts/models/),
instantiating these classes will check for the presence AND type of the attributes.
If you omit the [`Args`](../../../reference/api#django_components.Component.Args),
[`Kwargs`](../../../reference/api#django_components.Component.Kwargs), or
[`Slots`](../../../reference/api#django_components.Component.Slots) classes,
or set them to `None`, the inputs will be passed as plain lists or dictionaries,
and will not be validated.
```python
from typing_extensions import NamedTuple, TypedDict
from django.template import Context
from django_components import Component, Slot, SlotInput
# The data available to the `footer` scoped slot
class ButtonFooterSlotData(TypedDict):
value: int
# Define the component with the types
class Button(Component):
class Args(NamedTuple):
name: str
class Kwargs(NamedTuple):
surname: str
age: int
maybe_var: Optional[int] = None # May be omitted
class Slots(NamedTuple):
# Use `SlotInput` to allow slots to be given as `Slot` instance,
# plain string, or a function that returns a string.
my_slot: Optional[SlotInput] = None
# Use `Slot` to allow ONLY `Slot` instances.
# The generic is optional, and it specifies the data available
# to the slot function.
footer: Slot[ButtonFooterSlotData]
# Add type hints to the data method
def get_template_data(self, args: Args, kwargs: Kwargs, slots: Slots, context: Context):
# The parameters are instances of the classes we defined
assert isinstance(args, Button.Args)
assert isinstance(kwargs, Button.Kwargs)
assert isinstance(slots, Button.Slots)
args.name # str
kwargs.age # int
slots.footer # Slot[ButtonFooterSlotData]
# Add type hints to the render call
Button.render(
args=Button.Args(
name="John",
),
kwargs=Button.Kwargs(
surname="Doe",
age=30,
),
slots=Button.Slots(
footer=Slot(lambda ctx: "Click me!"),
),
)
```
If you don't want to validate some parts, set them to `None` or omit them.
The following will validate only the keyword inputs:
```python
class Button(Component):
# We could also omit these
Args = None
Slots = None
class Kwargs(NamedTuple):
name: str
age: int
# Only `kwargs` is instantiated. `args` and `slots` are not.
def get_template_data(self, args, kwargs: Kwargs, slots, context: Context):
assert isinstance(args, list)
assert isinstance(slots, dict)
assert isinstance(kwargs, Button.Kwargs)
args[0] # str
slots["footer"] # Slot[ButtonFooterSlotData]
kwargs.age # int
```
!!! info
Components can receive slots as strings, functions, or instances of [`Slot`](../../../reference/api#django_components.Slot).
Internally these are all normalized to instances of [`Slot`](../../../reference/api#django_components.Slot).
Therefore, the `slots` dictionary available in data methods (like
[`get_template_data()`](../../../reference/api#django_components.Component.get_template_data))
will always be a dictionary of [`Slot`](../../../reference/api#django_components.Slot) instances.
To correctly type this dictionary, you should set the fields of `Slots` to
[`Slot`](../../../reference/api#django_components.Slot) or [`SlotInput`](../../../reference/api#django_components.SlotInput):
[`SlotInput`](../../../reference/api#django_components.SlotInput) is a union of `Slot`, string, and function types.
## Typing data
You can use [`Component.TemplateData`](../../../reference/api#django_components.Component.TemplateData),
[`Component.JsData`](../../../reference/api#django_components.Component.JsData),
and [`Component.CssData`](../../../reference/api#django_components.Component.CssData) to type the data returned from [`get_template_data()`](../../../reference/api#django_components.Component.get_template_data), [`get_js_data()`](../../../reference/api#django_components.Component.get_js_data), and [`get_css_data()`](../../../reference/api#django_components.Component.get_css_data).
When you set these classes, at render time they will be instantiated with the data returned from these methods.
This way, each component can have runtime validation of the returned data:
- When you use [`NamedTuple`](https://docs.python.org/3/library/typing.html#typing.NamedTuple)
or [`@dataclass`](https://docs.python.org/3/library/dataclasses.html#dataclasses.dataclass),
instantiating these classes will check ONLY for the presence of the attributes.
- When you use [Pydantic models](https://docs.pydantic.dev/latest/concepts/models/),
instantiating these classes will check for the presence AND type of the attributes.
If you omit the [`TemplateData`](../../../reference/api#django_components.Component.TemplateData),
[`JsData`](../../../reference/api#django_components.Component.JsData), or
[`CssData`](../../../reference/api#django_components.Component.CssData) classes,
or set them to `None`, the validation and instantiation will be skipped.
```python
from typing import NamedTuple
from django_components import Component
class Button(Component):
class TemplateData(NamedTuple):
data1: str
data2: int
class JsData(NamedTuple):
js_data1: str
js_data2: int
class CssData(NamedTuple):
css_data1: str
css_data2: int
def get_template_data(self, args, kwargs, slots, context):
return {
"data1": "...",
"data2": 123,
}
def get_js_data(self, args, kwargs, slots, context):
return {
"js_data1": "...",
"js_data2": 123,
}
def get_css_data(self, args, kwargs, slots, context):
return {
"css_data1": "...",
"css_data2": 123,
}
```
For each data method, you can either return a plain dictionary with the data, or an instance of the respective data class directly.
```python
from typing import NamedTuple
from django_components import Component
class Button(Component):
class TemplateData(NamedTuple):
data1: str
data2: int
class JsData(NamedTuple):
js_data1: str
js_data2: int
class CssData(NamedTuple):
css_data1: str
css_data2: int
def get_template_data(self, args, kwargs, slots, context):
return Button.TemplateData(
data1="...",
data2=123,
)
def get_js_data(self, args, kwargs, slots, context):
return Button.JsData(
js_data1="...",
js_data2=123,
)
def get_css_data(self, args, kwargs, slots, context):
return Button.CssData(
css_data1="...",
css_data2=123,
)
```
## Custom types
We recommend to use [`NamedTuple`](https://docs.python.org/3/library/typing.html#typing.NamedTuple)
for the `Args` class, and [`NamedTuple`](https://docs.python.org/3/library/typing.html#typing.NamedTuple),
[dataclasses](https://docs.python.org/3/library/dataclasses.html#dataclasses.dataclass),
or [Pydantic models](https://docs.pydantic.dev/latest/concepts/models/)
for `Kwargs`, `Slots`, `TemplateData`, `JsData`, and `CssData` classes.
However, you can use any class, as long as they meet the conditions below.
For example, here is how you can use [Pydantic models](https://docs.pydantic.dev/latest/concepts/models/)
to validate the inputs at runtime.
```py
from django_components import Component
from pydantic import BaseModel
class Table(Component):
class Kwargs(BaseModel):
name: str
age: int
def get_template_data(self, args, kwargs, slots, context):
assert isinstance(kwargs, Table.Kwargs)
Table.render(
kwargs=Table.Kwargs(name="John", age=30),
)
```
### `Args` class
The [`Args`](../../../reference/api#django_components.Component.Args) class
represents a list of positional arguments. It must meet two conditions:
1. The constructor for the `Args` class must accept positional arguments.
```py
Args(*args)
```
2. The `Args` instance must be convertable to a list.
```py
list(Args(1, 2, 3))
```
To implement the conversion to a list, you can implement the `__iter__()` method:
```py
class MyClass:
def __init__(self):
self.x = 1
self.y = 2
def __iter__(self):
return iter([('x', self.x), ('y', self.y)])
```
### Dictionary classes
On the other hand, other types
([`Kwargs`](../../../reference/api#django_components.Component.Kwargs),
[`Slots`](../../../reference/api#django_components.Component.Slots),
[`TemplateData`](../../../reference/api#django_components.Component.TemplateData),
[`JsData`](../../../reference/api#django_components.Component.JsData),
and [`CssData`](../../../reference/api#django_components.Component.CssData))
represent dictionaries. They must meet these two conditions:
1. The constructor must accept keyword arguments.
```py
Kwargs(**kwargs)
Slots(**slots)
```
2. The instance must be convertable to a dictionary.
```py
dict(Kwargs(a=1, b=2))
dict(Slots(a=1, b=2))
```
To implement the conversion to a dictionary, you can implement either:
1. `_asdict()` method
```py
class MyClass:
def __init__(self):
self.x = 1
self.y = 2
def _asdict(self):
return {'x': self.x, 'y': self.y}
```
2. Or make the class dict-like with `__iter__()` and `__getitem__()`
```py
class MyClass:
def __init__(self):
self.x = 1
self.y = 2
def __iter__(self):
return iter([('x', self.x), ('y', self.y)])
def __getitem__(self, key):
return getattr(self, key)
```
## Passing variadic args and kwargs
You may have a component that accepts any number of args or kwargs.
However, this cannot be described with the current Python's typing system (as of v0.140).
As a workaround:
- For a variable number of positional arguments (`*args`), set a positional argument that accepts a list of values:
```py
class Table(Component):
class Args(NamedTuple):
args: List[str]
Table.render(
args=Table.Args(args=["a", "b", "c"]),
)
```
- For a variable number of keyword arguments (`**kwargs`), set a keyword argument that accepts a dictionary of values:
```py
class Table(Component):
class Kwargs(NamedTuple):
variable: str
another: int
# Pass any extra keys under `extra`
extra: Dict[str, any]
Table.render(
kwargs=Table.Kwargs(
variable="a",
another=1,
extra={"foo": "bar"},
),
)
```
## Handling no args or no kwargs
To declare that a component accepts no args, kwargs, etc, define the types with no attributes using the `pass` keyword:
```py
from typing import NamedTuple
from django_components import Component
class Button(Component):
class Args(NamedTuple):
pass
class Kwargs(NamedTuple):
pass
class Slots(NamedTuple):
pass
```
This can get repetitive, so we added a [`Empty`](../../../reference/api#django_components.Empty) type to make it easier:
```py
from django_components import Component, Empty
class Button(Component):
Args = Empty
Kwargs = Empty
Slots = Empty
```
## Subclassing
Subclassing components with types is simple.
Since each type class is a separate class attribute, you can just override them in the Component subclass.
In the example below, `ButtonExtra` inherits `Kwargs` from `Button`, but overrides the `Args` class.
```py
from django_components import Component, Empty
class Button(Component):
class Args(NamedTuple):
size: int
class Kwargs(NamedTuple):
color: str
class ButtonExtra(Button):
class Args(NamedTuple):
name: str
size: int
# Stil works the same way!
ButtonExtra.render(
args=ButtonExtra.Args(name="John", size=30),
kwargs=ButtonExtra.Kwargs(color="red"),
)
```
The only difference is when it comes to type hints to the data methods like
[`get_template_data()`](../../../reference/api#django_components.Component.get_template_data).
When you define the nested classes like `Args` and `Kwargs` directly on the class, you
can reference them just by their class name (`Args` and `Kwargs`).
But when you have a Component subclass, and it uses `Args` or `Kwargs` from the parent,
you will have to reference the type as a [forward reference](https://peps.python.org/pep-0563/#forward-references), including the full name of the component
(`Button.Args` and `Button.Kwargs`).
Compare the following:
```py
class Button(Component):
class Args(NamedTuple):
size: int
class Kwargs(NamedTuple):
color: str
# Both `Args` and `Kwargs` are defined on the class
def get_template_data(self, args: Args, kwargs: Kwargs, slots, context):
pass
class ButtonExtra(Button):
class Args(NamedTuple):
name: str
size: int
# `Args` is defined on the subclass, `Kwargs` is defined on the parent
def get_template_data(self, args: Args, kwargs: "ButtonExtra.Kwargs", slots, context):
pass
class ButtonSame(Button):
# Both `Args` and `Kwargs` are defined on the parent
def get_template_data(self, args: "ButtonSame.Args", kwargs: "ButtonSame.Kwargs", slots, context):
pass
```
## Runtime type validation
When you add types to your component, and implement
them as [`NamedTuple`](https://docs.python.org/3/library/typing.html#typing.NamedTuple) or [`dataclass`](https://docs.python.org/3/library/dataclasses.html#dataclasses.dataclass), the validation will check only for the presence of the attributes.
So this will not catch if you pass a string to an `int` attribute.
To enable runtime type validation, you need to use [Pydantic models](https://docs.pydantic.dev/latest/concepts/models/), and install the [`djc-ext-pydantic`](https://github.com/django-components/djc-ext-pydantic) extension.
The `djc-ext-pydantic` extension ensures compatibility between django-components' classes such as `Component`, or `Slot` and Pydantic models.
First install the extension:
```bash
pip install djc-ext-pydantic
```
And then add the extension to your project:
```py
COMPONENTS = {
"extensions": [
"djc_pydantic.PydanticExtension",
],
}
```
<!-- TODO_V1 - REMOVE IN v1 -->
## Migrating from generics to class attributes
In versions 0.92 to 0.139 (inclusive), the component typing was specified through generics.
Since v0.140, the types must be specified as class attributes of the [Component](../../../reference/api#django_components.Component) class -
[`Args`](../../../reference/api#django_components.Component.Args),
[`Kwargs`](../../../reference/api#django_components.Component.Kwargs),
[`Slots`](../../../reference/api#django_components.Component.Slots),
[`TemplateData`](../../../reference/api#django_components.Component.TemplateData),
[`JsData`](../../../reference/api#django_components.Component.JsData),
and [`CssData`](../../../reference/api#django_components.Component.CssData).
This change was necessary to make it possible to subclass components. Subclassing with generics was otherwise too complicated. Read the discussion [here](https://github.com/django-components/django-components/issues/1122).
Because of this change, the [`Component.render()`](../../../reference/api#django_components.Component.render)
method is no longer typed.
To type-check the inputs, you should wrap the inputs in [`Component.Args`](../../../reference/api#django_components.Component.Args),
[`Component.Kwargs`](../../../reference/api#django_components.Component.Kwargs),
[`Component.Slots`](../../../reference/api#django_components.Component.Slots), etc.
For example, if you had a component like this:
```py
from typing import NotRequired, Tuple, TypedDict
from django_components import Component, Slot, SlotInput
ButtonArgs = Tuple[int, str]
class ButtonKwargs(TypedDict):
variable: str
another: int
maybe_var: NotRequired[int] # May be omitted
class ButtonSlots(TypedDict):
# Use `SlotInput` to allow slots to be given as `Slot` instance,
# plain string, or a function that returns a string.
my_slot: NotRequired[SlotInput]
# Use `Slot` to allow ONLY `Slot` instances.
another_slot: Slot
ButtonType = Component[ButtonArgs, ButtonKwargs, ButtonSlots]
class Button(ButtonType):
def get_context_data(self, *args, **kwargs):
self.input.args[0] # int
self.input.kwargs["variable"] # str
self.input.slots["my_slot"] # Slot[MySlotData]
Button.render(
args=(1, "hello"),
kwargs={
"variable": "world",
"another": 123,
},
slots={
"my_slot": "...",
"another_slot": Slot(lambda ctx: ...),
},
)
```
The steps to migrate are:
1. Convert all the types (`ButtonArgs`, `ButtonKwargs`, `ButtonSlots`) to subclasses
of [`NamedTuple`](https://docs.python.org/3/library/typing.html#typing.NamedTuple).
2. Move these types inside the Component class (`Button`), and rename them to `Args`, `Kwargs`, and `Slots`.
3. If you defined typing for the data methods (like [`get_context_data()`](../../../reference/api#django_components.Component.get_context_data)), move them inside the Component class, and rename them to `TemplateData`, `JsData`, and `CssData`.
4. Remove the `Component` generic.
5. If you accessed the `args`, `kwargs`, or `slots` attributes via
[`self.input`](../../../reference/api#django_components.Component.input), you will need to add the type hints yourself, because [`self.input`](../../../reference/api#django_components.Component.input) is no longer typed.
Otherwise, you may use [`Component.get_template_data()`](../../../reference/api#django_components.Component.get_template_data) instead of [`get_context_data()`](../../../reference/api#django_components.Component.get_context_data), as `get_template_data()` receives `args`, `kwargs`, `slots` and `context` as arguments. You will still need to add the type hints yourself.
6. Lastly, you will need to update the [`Component.render()`](../../../reference/api#django_components.Component.render)
calls to wrap the inputs in [`Component.Args`](../../../reference/api#django_components.Component.Args), [`Component.Kwargs`](../../../reference/api#django_components.Component.Kwargs), and [`Component.Slots`](../../../reference/api#django_components.Component.Slots), to manually add type hints.
Thus, the code above will become:
```py
from typing import NamedTuple, Optional
from django.template import Context
from django_components import Component, Slot, SlotInput
# The Component class does not take any generics
class Button(Component):
# Types are now defined inside the component class
class Args(NamedTuple):
size: int
text: str
class Kwargs(NamedTuple):
variable: str
another: int
maybe_var: Optional[int] = None # May be omitted
class Slots(NamedTuple):
# Use `SlotInput` to allow slots to be given as `Slot` instance,
# plain string, or a function that returns a string.
my_slot: Optional[SlotInput] = None
# Use `Slot` to allow ONLY `Slot` instances.
another_slot: Slot
# The args, kwargs, slots are instances of the component's Args, Kwargs, and Slots classes
def get_template_data(self, args: Args, kwargs: Kwargs, slots: Slots, context: Context):
args.size # int
kwargs.variable # str
slots.my_slot # Slot[MySlotData]
Button.render(
# Wrap the inputs in the component's Args, Kwargs, and Slots classes
args=Button.Args(1, "hello"),
kwargs=Button.Kwargs(
variable="world",
another=123,
),
slots=Button.Slots(
my_slot="...",
another_slot=Slot(lambda ctx: ...),
),
)
```

View file

@ -52,7 +52,8 @@ Be sure to prefix your rules with unique CSS class like `calendar`, so the CSS d
This CSS will be inserted into the page as an inlined `<style>` tag, at the position defined by
[`{% component_css_dependencies %}`](../../reference/template_tags#component_css_dependencies),
or at the end of the inside the `<head>` tag (See [JS and CSS output locations](../../concepts/advanced/rendering_js_css/#js-and-css-output-locations)).
or at the end of the inside the `<head>` tag
(See [Default JS / CSS locations](../../concepts/advanced/rendering_js_css#default-js-css-locations)).
So in your HTML, you may see something like this:
@ -103,7 +104,7 @@ This makes all variables defined only be defined inside this component and not a
Similarly to CSS, JS will be inserted into the page as an inlined `<script>` tag, at the position defined by
[`{% component_js_dependencies %}`](../../reference/template_tags#component_js_dependencies),
or at the end of the inside the `<body>` tag (See [JS and CSS output locations](../../concepts/advanced/rendering_js_css/#js-and-css-output-locations)).
or at the end of the inside the `<body>` tag (See [Default JS / CSS locations](../../concepts/advanced/rendering_js_css#default-js-css-locations)).
So in your HTML, you may see something like this:
@ -184,7 +185,7 @@ class Calendar(Component):
js_file = "calendar.js" # <--- new
css_file = "calendar.css" # <--- new
def get_context_data(self):
def get_template_data(self, args, kwargs, slots, context):
return {
"date": "1970-01-01",
}
@ -225,7 +226,7 @@ automatically embed the associated JS and CSS.
Your components may depend on third-party packages or styling, or other shared logic.
To load these additional dependencies, you can use a nested [`Media` class](../../reference/api#django_components.Component.Media).
This `Media` class behaves similarly to [Django's Media class](https://docs.djangoproject.com/en/5.1/topics/forms/media/#assets-as-a-static-definition),
This `Media` class behaves similarly to [Django's Media class](https://docs.djangoproject.com/en/5.2/topics/forms/media/#assets-as-a-static-definition),
with a few differences:
1. Our Media class accepts various formats for the JS and CSS files: either a single file, a list, or (CSS-only) a dictonary (see below).
@ -256,7 +257,7 @@ class Calendar(Component):
"https://unpkg.com/tailwindcss@^2/dist/tailwind.min.css", # Tailwind
]
def get_context_data(self):
def get_template_data(self, args, kwargs, slots, context):
return {
"date": "1970-01-01",
}
@ -274,7 +275,7 @@ class Calendar(Component):
!!! info
The `Media` nested class is shaped based on [Django's Media class](https://docs.djangoproject.com/en/5.1/topics/forms/media/).
The `Media` nested class is shaped based on [Django's Media class](https://docs.djangoproject.com/en/5.2/topics/forms/media/).
As such, django-components allows multiple formats to define the nested Media class:

View file

@ -134,7 +134,7 @@ Which will render as:
{% endcomponent %}
```
### 5. Wait, there's a bug
### 4. Wait, there's a bug
There is a mistake in our code! `2024-12-13` is Friday, so that's fine. But if we updated
the to `2024-12-14`, which is Saturday, our template from previous step would render this:
@ -158,7 +158,7 @@ the to `2024-12-14`, which is Saturday, our template from previous step would re
The first instance rendered `2024-12-16`, while the rest rendered `2024-12-14`!
Why? Remember that in the [`get_context_data()`](../../reference/api#django_components.Component.get_context_data)
Why? Remember that in the [`get_template_data()`](../../reference/api#django_components.Component.get_template_data)
method of our Calendar component, we pre-process the date. If the date falls on Saturday or Sunday, we shift it to next Monday:
```python title="[project root]/components/calendar/calendar.py"
@ -174,11 +174,11 @@ def to_workweek_date(d: date):
class Calendar(Component):
template_file = "calendar.html"
...
def get_context_data(self, date: date, extra_class: str | None = None):
workweek_date = to_workweek_date(date)
def get_template_data(self, args, kwargs, slots, context):
workweek_date = to_workweek_date(kwargs["date"])
return {
"date": workweek_date,
"extra_class": extra_class,
"extra_class": kwargs.get("extra_class", "text-blue"),
}
```
@ -189,7 +189,8 @@ which is NOT the same as the `date` variable used inside Calendar's template.
We want to use the same `date` variable that's used inside Calendar's template.
Luckily, django-components allows passing data to the slot, also known as [Scoped slots](../../concepts/fundamentals/slots#scoped-slots).
Luckily, django-components allows [passing data to slots](../../concepts/fundamentals/slots#slot-data),
also known as [Scoped slots](https://vuejs.org/guide/components/slots#scoped-slots).
This consists of two steps:
@ -284,7 +285,12 @@ each time:
Generally, slots are more flexible - you can access the slot data, even the original slot content.
Thus, slots behave more like functions that render content based on their context.
On the other hand, variables are static - the variable you pass to a component is what will be used.
On the other hand, variables are simpler - the variable you pass to a component is what will be used.
Moreover, slots are treated as part of the template - for example the CSS scoping (work in progress)
is applied to the slot content too.
---
So far we've rendered components using template tag. [Next, lets explore other ways to render components ➡️]
(./rendering_components.md)

View file

@ -30,7 +30,7 @@ class Calendar(Component):
js_file = "calendar.js"
css_file = "calendar.css"
def get_context_data(self):
def get_template_data(self, args, kwargs, slots, context):
return {
"date": "1970-01-01",
}
@ -55,7 +55,7 @@ by calling `{% load component_tags %}` inside the template.
like `{% component "calendar" / %}`.
`ComponentRegistries` also make it possible to group and share components as standalone packages.
[Learn more here](../../concepts/advanced/authoring_component_libraries).
[Learn more here](../../concepts/advanced/component_libraries).
!!! note
@ -102,7 +102,7 @@ and render the component inside a template:
!!! info
Component tags should end with `/` if they do not contain any [Slot fills](../../concepts/fundamentals/slots#slot-fills).
Component tags should end with `/` if they do not contain any [Slot fills](../../concepts/fundamentals/slots).
But you can also use `{% endcomponent %}` instead:
```htmldjango
@ -159,7 +159,8 @@ and keeping your CSS and Javascript in the static directory.
Remember that you can use
[`{% component_js_dependencies %}`](../../reference/template_tags#component_js_dependencies)
and [`{% component_css_dependencies %}`](../../reference/template_tags#component_css_dependencies)
to change where the `<script>` and `<style>` tags will be rendered (See [JS and CSS output locations](../../concepts/advanced/rendering_js_css/#js-and-css-output-locations)).
to change where the `<script>` and `<style>` tags will be rendered
(See [Default JS / CSS locations](../../concepts/advanced/rendering_js_css#default-js-css-locations)).
!!! info

View file

@ -10,7 +10,7 @@ What we want is to be able to use the Calendar component within the template lik
### 1. Understading component inputs
In section [Create your first component](../your_first_component), we defined
the [`get_context_data()`](../../reference/api#django_components.Component.get_context_data) method
the [`get_template_data()`](../../reference/api#django_components.Component.get_template_data) method
that defines what variables will be available within the template:
```python title="[project root]/components/calendar/calendar.py"
@ -20,13 +20,13 @@ from django_components import Component, register
class Calendar(Component):
template_file = "calendar.html"
...
def get_context_data(self):
def get_template_data(self, args, kwargs, slots, context):
return {
"date": "1970-01-01",
}
```
What we didn't say is that [`get_context_data()`](../../reference/api#django_components.Component.get_context_data)
What we didn't say is that [`get_template_data()`](../../reference/api#django_components.Component.get_template_data)
actually receives the args and kwargs that were passed to a component.
So if we call a component with a `date` and `extra_class` keywords:
@ -38,7 +38,10 @@ So if we call a component with a `date` and `extra_class` keywords:
This is the same as calling:
```py
Calendar.get_context_data(date="2024-12-13", extra_class="text-red")
Calendar.get_template_data(
args=[],
kwargs={"date": "2024-12-13", "extra_class": "text-red"},
)
```
And same applies to positional arguments, or mixing args and kwargs, where:
@ -50,13 +53,16 @@ And same applies to positional arguments, or mixing args and kwargs, where:
is same as
```py
Calendar.get_context_data("2024-12-13", extra_class="text-red")
Calendar.get_template_data(
args=["2024-12-13"],
kwargs={"extra_class": "text-red"},
)
```
### 2. Define inputs for `get_context_data`
### 2. Define inputs
Let's put this to test. We want to pass `date` and `extra_class` kwargs to the component.
And so, we can write the [`get_context_data()`](../../reference/api#django_components.Component.get_context_data)
And so, we can write the [`get_template_data()`](../../reference/api#django_components.Component.get_template_data)
method such that it expects those parameters:
```python title="[project root]/components/calendar/calendar.py"
@ -68,53 +74,47 @@ from django_components import Component, register
class Calendar(Component):
template_file = "calendar.html"
...
def get_context_data(self, date: date, extra_class: str = "text-blue"):
def get_template_data(self, args, kwargs, slots, context):
return {
"date": date,
"extra_class": extra_class,
"date": kwargs["date"],
"extra_class": kwargs.get("extra_class", "text-blue"),
}
```
!!! info
Since [`get_context_data()`](../../reference/api#django_components.Component.get_context_data)
is just a regular Python function, type hints annotations work the same way as anywhere else.
!!! warning
Since [`get_context_data()`](../../reference/api#django_components.Component.get_context_data)
is just a regular Python function, it will raise TypeError if it receives incorrect parameters.
Since `extra_class` is optional in the function signature, it's optional also in the template.
So both following calls are valid:
```htmldjango
{% component "calendar" "2024-12-13" / %}
{% component "calendar" "2024-12-13" extra_class="text-red" / %}
{% component "calendar" date="2024-12-13" / %}
{% component "calendar" date="2024-12-13" extra_class="text-red" / %}
```
However, `date` is required. Thus we MUST provide it. Same with regular Python functions,
`date` can be set either as positional or keyword argument. But either way it MUST be set:
!!! warning
```htmldjango
{% component "calendar" "2024-12-13" / %}
{% component "calendar" extra_class="text-red" date="2024-12-13" / %}
[`get_template_data()`](../../reference/api#django_components.Component.get_template_data)
differentiates between positional and keyword arguments,
so you have to make sure to pass the arguments correctly.
{% component "calendar" extra_class="text-red" / %}
```
Since `date` is expected to be a keyword argument, it MUST be provided as such:
### 3. Process inputs in `get_context_data`
```htmldjango
`date` is kwarg
{% component "calendar" date="2024-12-13" / %}
The [`get_context_data()`](../../reference/api#django_components.Component.get_context_data)
`date` is arg
{% component "calendar" "2024-12-13" / %}
```
### 3. Process inputs
The [`get_template_data()`](../../reference/api#django_components.Component.get_template_data)
method is powerful, because it allows us to decouple
component inputs from the template variables. In other words, we can pre-process
the component inputs, and massage them into a shape that's most appropriate for
what the template needs. And it also allows us to pass in static data into the template.
Imagine our component receives data from the database that looks like below
([taken from Django](https://docs.djangoproject.com/en/5.1/ref/templates/builtins/#regroup)).
([taken from Django](https://docs.djangoproject.com/en/5.2/ref/templates/builtins/#regroup)).
```py
cities = [
@ -160,12 +160,14 @@ cities_by_pop = [
]
```
Without the `get_context_data()` method, we'd have to either:
Without the [`get_template_data()`](../../reference/api#django_components.Component.get_template_data) method,
we'd have to either:
1. Pre-process the data in Python before passing it to the components.
2. Define a Django filter or template tag to take the data and process it on the spot.
Instead, with `get_context_data()`, we can keep this transformation private to this component,
Instead, with [`get_template_data()`](../../reference/api#django_components.Component.get_template_data),
we can keep this transformation private to this component,
and keep the rest of the codebase clean.
```py
@ -176,13 +178,14 @@ def group_by_pop(data):
class PopulationTable(Component):
template_file = "population_table.html"
def get_context_data(self, data):
def get_template_data(self, args, kwargs, slots, context):
return {
"data": group_by_pop(data),
"data": group_by_pop(kwargs["data"]),
}
```
Similarly we can make use of `get_context_data()` to pre-process the date that was given to the component:
Similarly we can make use of [`get_template_data()`](../../reference/api#django_components.Component.get_template_data)
to pre-process the date that was given to the component:
```python title="[project root]/components/calendar/calendar.py"
from datetime import date
@ -197,17 +200,17 @@ def to_workweek_date(d: date):
class Calendar(Component):
template_file = "calendar.html"
...
def get_context_data(self, date: date, extra_class: str = "text-blue"):
workweek_date = to_workweek_date(date) # <--- new
def get_template_data(self, args, kwargs, slots, context):
workweek_date = to_workweek_date(kwargs["date"]) # <--- new
return {
"date": workweek_date, # <--- changed
"extra_class": extra_class,
"extra_class": kwargs.get("extra_class", "text-blue"),
}
```
### 4. Pass inputs to components
Once we're happy with `Calendar.get_contex_data()`, we can update our templates to use
Once we're happy with `Calendar.get_template_data()`, we can update our templates to use
the parametrized version of the component:
```htmldjango
@ -217,14 +220,10 @@ the parametrized version of the component:
</div>
```
---
Next, you will learn [how to use slots give your components even more flexibility ➡️](./adding_slots.md)
### 5. Add defaults
In our example, we've set the `extra_class` to default to `"text-blue"` by setting it in the
[`get_context_data()`](../../reference/api#django_components.Component.get_context_data)
[`get_template_data()`](../../reference/api#django_components.Component.get_template_data)
method.
However, you may want to use the same default value in multiple methods, like
@ -248,10 +247,14 @@ class Calendar(Component):
class Defaults: # <--- new
extra_class = "text-blue"
def get_context_data(self, date: date, extra_class: str): # <--- changed
workweek_date = to_workweek_date(date)
def get_template_data(self, args, kwargs, slots, context):
workweek_date = to_workweek_date(kwargs["date"])
return {
"date": workweek_date,
"extra_class": extra_class,
"extra_class": kwargs["extra_class"], # <--- changed
}
```
---
Next, you will learn [how to use slots give your components even more flexibility ➡️](./adding_slots.md)

View file

@ -19,13 +19,13 @@ class Calendar(Component):
js_file = "calendar.js"
css_file = "calendar.css"
def get_context_data(self):
def get_template_data(self, args, kwargs, slots, context):
return {
"date": "1970-01-01",
}
```
### 1. Render the template that contains the `{% component %}` tag
### 1. Render the template
If you have embedded the component in a Django template using the
[`{% component %}`](../../reference/template_tags#component) tag:
@ -39,7 +39,7 @@ If you have embedded the component in a Django template using the
You can simply render the template with the Django tooling:
#### With [`django.shortcuts.render()`](https://docs.djangoproject.com/en/5.1/topics/http/shortcuts/#render)
#### With [`django.shortcuts.render()`](https://docs.djangoproject.com/en/5.2/topics/http/shortcuts/#render)
```python
from django.shortcuts import render
@ -48,9 +48,9 @@ context = {"date": "2024-12-13"}
rendered_template = render(request, "my_template.html", context)
```
#### With [`Template.render()`](https://docs.djangoproject.com/en/5.1/ref/templates/api/#django.template.Template.render)
#### With [`Template.render()`](https://docs.djangoproject.com/en/5.2/ref/templates/api/#django.template.Template.render)
Either loading the template with [`get_template()`](https://docs.djangoproject.com/en/5.1/topics/templates/#django.template.loader.get_template):
Either loading the template with [`get_template()`](https://docs.djangoproject.com/en/5.2/topics/templates/#django.template.loader.get_template):
```python
from django.template.loader import get_template
@ -60,7 +60,7 @@ context = {"date": "2024-12-13"}
rendered_template = template.render(context)
```
Or creating a new [`Template`](https://docs.djangoproject.com/en/5.1/ref/templates/api/#django.template.Template) instance:
Or creating a new [`Template`](https://docs.djangoproject.com/en/5.2/ref/templates/api/#django.template.Template) instance:
```python
from django.template import Template
@ -74,7 +74,7 @@ template = Template("""
rendered_template = template.render()
```
### 2. Render the component directly with [`Component.render()`](../../reference/api#django_components.Component.render)
### 2. Render the component
You can also render the component directly with [`Component.render()`](../../reference/api#django_components.Component.render), without wrapping the component in a template.
@ -113,15 +113,15 @@ rendered_component = calendar.render(
rendered_component = calendar.render(request=request)
```
The `request` object is required for some of the component's features, like using [Django's context processors](https://docs.djangoproject.com/en/5.1/ref/templates/api/#django.template.RequestContext).
The `request` object is required for some of the component's features, like using [Django's context processors](https://docs.djangoproject.com/en/5.2/ref/templates/api/#django.template.RequestContext).
### 3. Render the component directly with [`Component.render_to_response()`](../../reference/api#django_components.Component.render_to_response)
### 3. Render the component to HttpResponse
A common pattern in Django is to render the component and then return the resulting HTML as a response to an HTTP request.
For this, you can use the [`Component.render_to_response()`](../../reference/api#django_components.Component.render_to_response) convenience method.
`render_to_response()` accepts the same args, kwargs, slots, and more, as [`Component.render()`](../../reference/api#django_components.Component.render), but wraps the result in an [`HttpResponse`](https://docs.djangoproject.com/en/5.1/ref/request-response/#django.http.HttpResponse).
`render_to_response()` accepts the same args, kwargs, slots, and more, as [`Component.render()`](../../reference/api#django_components.Component.render), but wraps the result in an [`HttpResponse`](https://docs.djangoproject.com/en/5.2/ref/request-response/#django.http.HttpResponse).
```python
from components.calendar import Calendar
@ -144,7 +144,7 @@ def my_view(request):
**Response class of `render_to_response`**
While `render` method returns a plain string, `render_to_response` wraps the rendered content in a "Response" class. By default, this is [`django.http.HttpResponse`](https://docs.djangoproject.com/en/5.1/ref/request-response/#django.http.HttpResponse).
While `render` method returns a plain string, `render_to_response` wraps the rendered content in a "Response" class. By default, this is [`django.http.HttpResponse`](https://docs.djangoproject.com/en/5.2/ref/request-response/#django.http.HttpResponse).
If you want to use a different Response class in `render_to_response`, set the [`Component.response_class`](../../reference/api#django_components.Component.response_class) attribute:
@ -159,3 +159,137 @@ def my_view(request):
class SimpleComponent(Component):
response_class = MyCustomResponse
```
### 4. Rendering slots
Slots content are automatically escaped by default to prevent XSS attacks.
In other words, it's as if you would be using Django's [`escape()`](https://docs.djangoproject.com/en/5.2/ref/templates/builtins/#std-templatefilter-escape) on the slot contents / result:
```python
from django.utils.html import escape
class Calendar(Component):
template = """
<div>
{% slot "date" default date=date / %}
</div>
"""
Calendar.render(
slots={
"date": escape("<b>Hello</b>"),
}
)
```
To disable escaping, you can wrap the slot string or slot result in Django's [`mark_safe()`](https://docs.djangoproject.com/en/5.2/ref/utils/#django.utils.safestring.mark_safe):
```py
Calendar.render(
slots={
# string
"date": mark_safe("<b>Hello</b>"),
# function
"date": lambda ctx: mark_safe("<b>Hello</b>"),
}
)
```
!!! info
Read more about Django's
[`format_html`](https://docs.djangoproject.com/en/5.2/ref/utils/#django.utils.html.format_html)
and [`mark_safe`](https://docs.djangoproject.com/en/5.2/ref/utils/#django.utils.safestring.mark_safe).
### 5. Component views and URLs
For web applications, it's common to define endpoints that serve HTML content (AKA views).
If this is your case, you can define the view request handlers directly on your component by using the nested[`Component.View`](../../reference/api#django_components.Component.View) class.
This is a great place for:
- Endpoints that render whole pages, if your component
is a page component.
- Endpoints that render the component as HTML fragments, to be used with HTMX or similar libraries.
Read more on [Component views and URLs](../../concepts/fundamentals/component_views_urls).
```djc_py title="[project root]/components/calendar.py"
from django_components import Component, ComponentView, register
@register("calendar")
class Calendar(Component):
template = """
<div class="calendar-component">
<div class="header">
{% slot "header" / %}
</div>
<div class="body">
Today's date is <span>{{ date }}</span>
</div>
</div>
"""
class View:
# Handle GET requests
def get(self, request, *args, **kwargs):
# Return HttpResponse with the rendered content
return Calendar.render_to_response(
request=request,
kwargs={
"date": request.GET.get("date", "2020-06-06"),
},
slots={
"header": "Calendar header",
},
)
```
!!! info
The View class supports all the same HTTP methods as Django's [`View`](https://docs.djangoproject.com/en/5.2/ref/class-based-views/base/#django.views.generic.base.View) class. These are:
`get()`, `post()`, `put()`, `patch()`, `delete()`, `head()`, `options()`, `trace()`
Each of these receive the [`HttpRequest`](https://docs.djangoproject.com/en/5.2/ref/request-response/#django.http.HttpRequest) object as the first argument.
Next, you need to set the URL for the component.
You can either:
1. Automatically assign the URL by setting the [`Component.View.public`](../../reference/api#django_components.ComponentView.public) attribute to `True`.
In this case, use [`get_component_url()`](../../reference/api#django_components.get_component_url) to get the URL for the component view.
```djc_py
from django_components import Component, get_component_url
class Calendar(Component):
class View:
public = True
url = get_component_url(Calendar)
```
2. Manually assign the URL by setting [`Component.as_view()`](../../reference/api#django_components.Component.as_view) to your `urlpatterns`:
```djc_py
from django.urls import path
from components.calendar import Calendar
urlpatterns = [
path("calendar/", Calendar.as_view()),
]
```
And with that, you're all set! When you visit the URL, the component will be rendered and the content will be returned.
The `get()`, `post()`, etc methods will receive the [`HttpRequest`](https://docs.djangoproject.com/en/5.2/ref/request-response/#django.http.HttpRequest) object as the first argument. So you can parametrize how the component is rendered for example by passing extra query parameters to the URL:
```
http://localhost:8000/calendar/?date=2024-12-13
```

View file

@ -35,8 +35,9 @@ document.querySelector(".calendar").onclick = function () {
```
```py title="calendar.py"
from django_components import Component
from django_components import Component, register
@register("calendar")
class Calendar(Component):
template_file = "calendar.html"
js_file = "calendar.js"
@ -72,7 +73,7 @@ class Calendar(Component):
!!! note
If you "inline" the HTML, JS and CSS code into the Python class, you can set up
[syntax highlighting](../../guides/setup/syntax_highlight) for better experience.
[syntax highlighting](../../concepts/fundamentals/single_file_components#syntax-highlighting) for better experience.
However, autocompletion / intellisense does not work with syntax highlighting.
We'll start by creating a component that defines only a Django template:
@ -104,7 +105,7 @@ Inside `calendar.html`, write:
```
In this example we've defined one template variable `date`. You can use any and as many variables as you like. These variables will be
defined in the Python file in [`get_context_data()`](../../reference/api#django_components.Component.get_context_data)
defined in the Python file in [`get_template_data()`](../../reference/api#django_components.Component.get_template_data)
when creating an instance of this component.
!!! note
@ -142,7 +143,7 @@ class Calendar(Component):
In `calendar.html`, we've used the variable `date`. So we need to define it for the template to work.
This is done using [`Component.get_context_data()`](../../reference/api#django_components.Component.get_context_data).
This is done using [`Component.get_template_data()`](../../reference/api#django_components.Component.get_template_data).
It's a function that returns a dictionary. The entries in this dictionary
will become available within the template as variables, e.g. as `{{ date }}`.
@ -152,7 +153,7 @@ from django_components import Component
class Calendar(Component):
template_file = "calendar.html"
def get_context_data(self):
def get_template_data(self, args, kwargs, slots, context):
return {
"date": "1970-01-01",
}

Some files were not shown because too many files have changed in this diff Show more