Compare commits

..

75 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
98 changed files with 1261 additions and 334 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

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

@ -1,5 +1,77 @@
# Release notes # Release notes
## v0.141.2
#### Fix
- Fix bug where JS and CSS were missing when `{% component %}` tag was inside `{% include %}` tag ([#1296](https://github.com/django-components/django-components/issues/1296))
## v0.141.1
#### Fix
- Components' JS and CSS scripts (e.g. from `Component.js` or `Component.js_file`) are now cached at class creation time.
This means that when you now restart the server while having a page opened in the browser,
the JS / CSS files are immediately available.
Previously, the JS/CSS were cached only after the components were rendered. So you had to reload
the page to trigger the rendering, in order to make the JS/CSS files available.
- Fix the default cache for JS / CSS scripts to be unbounded.
Previously, the default cache for the JS/CSS scripts (`LocMemCache`) was accidentally limited to 300 entries (~150 components).
- Do not send `template_rendered` signal when rendering a component with no template. ([#1277](https://github.com/django-components/django-components/issues/1277))
## v0.141.0
#### Feat
- New extension hooks `on_template_loaded`, `on_js_loaded`, `on_css_loaded`, and `on_template_compiled`
The first 3 hooks are called when Component's template / JS / CSS is loaded as a string.
The `on_template_compiled` hook is called when Component's template is compiled to a Template.
The `on_xx_loaded` hooks can modify the content by returning the new value.
```py
class MyExtension(ComponentExtension):
def on_template_loaded(self, ctx: OnTemplateLoadedContext) -> Optional[str]:
return ctx.content + "<!-- Hello! -->"
def on_js_loaded(self, ctx: OnJsLoadedContext) -> Optional[str]:
return ctx.content + "// Hello!"
def on_css_loaded(self, ctx: OnCssLoadedContext) -> Optional[str]:
return ctx.content + "/* Hello! */"
```
See all [Extension hooks](https://django-components.github.io/django-components/0.141.0/reference/extension_hooks/).
#### Fix
- Subclassing - Previously, if a parent component defined `Component.template` or `Component.template_file`, it's subclass would use the same `Template` instance.
This could lead to unexpected behavior, where a change to the template of the subclass would also change the template of the parent class.
Now, each subclass has it's own `Template` instance, and changes to the template of the subclass do not affect the template of the parent class.
- Fix Django failing to restart due to "TypeError: 'Dynamic' object is not iterable" ([#1232](https://github.com/django-components/django-components/issues/1232))
- Fix bug when error formatting failed when error value was not a string.
#### Refactor
- `components ext run` CLI command now allows to call only those extensions that actually have subcommands.
## v0.140.1
#### Fix
- Fix typo preventing benchmarking ([#1235](https://github.com/django-components/django-components/pull/1235))
## 🚨📢 v0.140.0 ## 🚨📢 v0.140.0
⚠️ Major release ⚠️ - Please test thoroughly before / after upgrading. ⚠️ Major release ⚠️ - Please test thoroughly before / after upgrading.
@ -64,7 +136,7 @@ Summary:
- `"append"` - `"append"`
- `"ignore"` - `"ignore"`
See [Dependencies rendering](https://django-components.github.io/django-components/0.140/concepts/advanced/rendering_js_css/) for more info. See [Dependencies rendering](https://django-components.github.io/django-components/0.140.1/concepts/advanced/rendering_js_css/) for more info.
**Typing** **Typing**
@ -88,8 +160,8 @@ Summary:
text: str text: str
``` ```
See [Migrating from generics to class attributes](https://django-components.github.io/django-components/0.140/concepts/fundamentals/typing_and_validation/#migrating-from-generics-to-class-attributes) for more info.
See [Migrating from generics to class attributes](https://django-components.github.io/django-components/0.140.1/concepts/fundamentals/typing_and_validation/#migrating-from-generics-to-class-attributes) for more info.
- Removed `EmptyTuple` and `EmptyDict` types. Instead, there is now a single `Empty` type. - Removed `EmptyTuple` and `EmptyDict` types. Instead, there is now a single `Empty` type.
```py ```py
@ -977,7 +1049,7 @@ Summary:
- Rendered HTML is left as-is. You can still process it with a different strategy later with `render_dependencies()`. - Rendered HTML is left as-is. You can still process it with a different strategy later with `render_dependencies()`.
- Used for inserting rendered HTML into other components. - Used for inserting rendered HTML into other components.
See [Dependencies rendering](https://django-components.github.io/django-components/0.140/concepts/advanced/rendering_js_css/) for more info. See [Dependencies rendering](https://django-components.github.io/django-components/0.140.1/concepts/advanced/rendering_js_css/) for more info.
- New `Component.args`, `Component.kwargs`, `Component.slots` attributes available on the component class itself. - New `Component.args`, `Component.kwargs`, `Component.slots` attributes available on the component class itself.
@ -1057,7 +1129,7 @@ Summary:
- Modify the rendered output after it has been rendered - Modify the rendered output after it has been rendered
- Handle errors - Handle errors
See [on_render](https://django-components.github.io/django-components/0.140/concepts/advanced/hooks/#on_render) for more info. See [on_render](https://django-components.github.io/django-components/0.140.1/concepts/advanced/hooks/#on_render) for more info.
- `get_component_url()` now optionally accepts `query` and `fragment` arguments. - `get_component_url()` now optionally accepts `query` and `fragment` arguments.
@ -1125,7 +1197,7 @@ Summary:
- `ComponentNode` instance if the slot was created as a default slot from a `{% component %}` tag. - `ComponentNode` instance if the slot was created as a default slot from a `{% component %}` tag.
- `None` if the slot was created from a string, function, or `Slot` instance. - `None` if the slot was created from a string, function, or `Slot` instance.
See [Slot metadata](https://django-components.github.io/django-components/0.140/concepts/fundamentals/slots/#slot-metadata). See [Slot metadata](https://django-components.github.io/django-components/0.140.1/concepts/fundamentals/slots/#slot-metadata).
- `{% fill %}` tag now accepts `body` kwarg to pass a Slot instance to fill. - `{% fill %}` tag now accepts `body` kwarg to pass a Slot instance to fill.
@ -1224,13 +1296,13 @@ Summary:
) )
``` ```
Read more on [Component caching](https://django-components.github.io/django-components/0.140/concepts/advanced/component_caching/). Read more on [Component caching](https://django-components.github.io/django-components/0.140.1/concepts/advanced/component_caching/).
- New extension hook `on_slot_rendered()` - New extension hook `on_slot_rendered()`
This hook is called when a slot is rendered, and allows you to access and/or modify the rendered result. This hook is called when a slot is rendered, and allows you to access and/or modify the rendered result.
This is used by the ["debug highlight" feature](https://django-components.github.io/django-components/0.140/guides/other/troubleshooting/#component-and-slot-highlighting). This is used by the ["debug highlight" feature](https://django-components.github.io/django-components/0.140.1/guides/other/troubleshooting/#component-and-slot-highlighting).
To modify the rendered result, return the new value: To modify the rendered result, return the new value:
@ -1242,7 +1314,7 @@ Summary:
If you don't want to modify the rendered result, return `None`. If you don't want to modify the rendered result, return `None`.
See all [Extension hooks](https://django-components.github.io/django-components/0.140/reference/extension_hooks/). See all [Extension hooks](https://django-components.github.io/django-components/0.140.1/reference/extension_hooks/).
- When creating extensions, the previous syntax with `ComponentExtension.ExtensionClass` was causing - When creating extensions, the previous syntax with `ComponentExtension.ExtensionClass` was causing
Mypy errors, because Mypy doesn't allow using class attributes as bases: Mypy errors, because Mypy doesn't allow using class attributes as bases:

View file

@ -24,8 +24,9 @@ A component in django-components can be as simple as a Django template and Pytho
```py ```py
# components/calendar/calendar.py # components/calendar/calendar.py
from django_components import Component from django_components import Component, register
@register("calendar")
class Calendar(Component): class Calendar(Component):
template_file = "calendar.html" template_file = "calendar.html"
``` ```
@ -56,8 +57,9 @@ document.querySelector(".calendar").onclick = () => {
```py ```py
# components/calendar/calendar.py # components/calendar/calendar.py
from django_components import Component from django_components import Component, register
@register("calendar")
class Calendar(Component): class Calendar(Component):
template_file = "calendar.html" template_file = "calendar.html"
js_file = "calendar.js" js_file = "calendar.js"
@ -552,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. 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/). - [djc-heroicons](https://pypi.org/project/djc-heroicons/): A component that renders icons from [Heroicons.com](https://heroicons.com/).

View file

@ -1 +1 @@
[[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]]] [[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]], [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]]] [[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]], [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]]] [[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]], [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]]] [[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]], [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]]] [[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]], [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]]] [[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]], [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]]] [[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]], [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]]] [[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]], [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]]] [[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], [1709, 0.19950735400001918], [1726, 0.19625152499997967], [1766, 0.20073733000003813], [1770, 0.20376683500001036], [1776, 0.19919827600000417], [1801, 0.2053688209999791]] [[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]], [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]]] [[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]], [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]]] [[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]], [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]]] [[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]], [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]]] [[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]], [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]]] [[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]], [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]]] [[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]], [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]]] [[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]], [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]]] [[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]], [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]]] [[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], [1709, 53963042.655257314], [1726, 53670369.245800875], [1766, 54220916.6140389], [1770, 54198077.75539557], [1776, 53906774.26269022], [1801, 54270509.344660625]] [[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], [1709, 54605449.27839023], [1726, 53697436.790693834], [1766, 54766004.5031032], [1770, 54878384.55144014], [1776, 53912680.86221259], [1801, 54851721.60114168]] [[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], [1709, 44253141.3491094], [1726, 44255192.14695785], [1766, 44318719.81072088], [1770, 44324863.95268679], [1776, 44244949.34121254], [1801, 44324676.21343578]] [[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], [1709, 44314453.63272547], [1726, 44318719.81072088], [1766, 44316671.57410231], [1770, 44205956.60747199], [1776, 44376021.4672124], [1801, 44322622.19567646]] [[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], [1709, 0.14028461383291016], [1726, 0.1369248426273554], [1766, 0.13857329097819557], [1770, 0.14073477092350728], [1776, 0.1385645020210802], [1801, 0.14040196312080028]] [[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], [1709, 0.07467771106069107], [1726, 0.07307627413528986], [1766, 0.0752258677863117], [1770, 0.07591381717343901], [1776, 0.0741750279629251], [1801, 0.07486521068773488]] [[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], [1709, 0.004181923314217816], [1726, 0.004054429932062044], [1766, 0.004133897799028137], [1770, 0.004257194320585938], [1776, 0.004117446125697445], [1801, 0.004200816754404154]] [[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], [1709, 0.0002646827752432008], [1726, 0.00025280056719810247], [1766, 0.00025904182642747317], [1770, 0.0002627038966898471], [1776, 0.00026058997620285855], [1801, 0.000260725493948419]] [[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], [1709, 0.21969118716012626], [1726, 0.21540413874268913], [1766, 0.2217946171557135], [1770, 0.22763817627917332], [1776, 0.21949736979633283], [1801, 0.22435444169386096]] [[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], [1709, 0.19950735400001918], [1726, 0.19625152499997967], [1766, 0.20073733000003813], [1770, 0.20376683500001036], [1776, 0.19919827600000417], [1801, 0.2053688209999791]] [[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], [1709, 54655941.05401974], [1726, 54668354.35558938], [1766, 55133687.30603648], [1770, 55072492.873806104], [1776, 55115246.19008138], [1801, 55131593.83007953]] [[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], [1709, 55404533.06087942], [1726, 55316304.695168346], [1766, 55588847.36277981], [1770, 55711653.6193069], [1776, 55635967.849223286], [1801, 55857133.82825839]] [[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], [1709, 44312575.95267366], [1726, 44314624.0], [1766, 44316671.95267803], [1770, 44318719.81072088], [1776, 44273651.87380721], [1801, 44453888.0]] [[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], [1709, 44312575.95267366], [1726, 44314624.0], [1766, 44312575.95267366], [1770, 44316671.95267803], [1776, 44437504.0], [1801, 44449792.0]] [[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], [1709, 0.26615604836262874], [1726, 0.25886008645727265], [1766, 0.2656287982807661], [1770, 0.2675580766089799], [1776, 0.2644825926606367], [1801, 0.26856188100049755]] [[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], [1709, 0.1508107336447194], [1726, 0.14754149544768042], [1766, 0.15304096778650703], [1770, 0.1536390943522132], [1776, 0.15209353551720362], [1801, 0.15356938175949056]] [[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], [1709, 0.004839972328668506], [1726, 0.004672193982353972], [1766, 0.004766899700580667], [1770, 0.004898703707479391], [1776, 0.004767500868992566], [1801, 0.004852423669122516]] [[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], [1709, 0.0005728459938648165], [1726, 0.0005428729701593198], [1766, 0.0005836611719634209], [1770, 0.0005880105852110292], [1776, 0.0005786218553806627], [1801, 0.0005751723828193878]] [[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], [1709, 0.22018038637622225], [1726, 0.2149156309515977], [1766, 0.2198640308272821], [1770, 0.22362192103085216], [1776, 0.21863830924562072], [1801, 0.22295218072522197]] [[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", "asv-version": "0.6.4",
"timestamp": 1745143636166 "timestamp": 1753049912703
} }

View file

@ -1 +1 @@
{"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.03723830100000214, 0.03327357099999517, [[1691, 1709, 0.03327357099999517, 0.03723830100000214]]], ["Components vs Django.timeraw_render_sm_subsequent('django')", "graphs/branch-master/django-5.1/djc-core-html-parser/machine-ci-linux/python-3.13/Components vs Django.timeraw_render_sm_subsequent.json", {}, 0, 0.00011641800000461444, 0.0001005780000014056, [[1691, 1709, 0.0001005780000014056, 0.00011641800000461444]]]]} {"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,8 +1,8 @@
<?xml version='1.0' encoding='utf-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-31T14:17:04Z</updated><entry><id>tag:django-components.asv,2025-03-31:/3489e338e3aeb103ea646e0f8aee41043fa61c99d14c7b57d874c9ebc1c79b22</id><title xml:lang="en">15.75% Components vs Django.timeraw_render_sm_subsequent('django')</title><updated>2025-03-31T14:17:04Z</updated><link href="index.html#Components vs Django.timeraw_render_sm_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_sm_subsequent?p-renderer=%27django%27&amp;commits=42818ad6ffb47bd650d8a379b84c3d48394f9f77-a6455d70f6c28ddbd4be8e58902f6cbc101e5ff3"&gt;15.75% 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; <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: 116μs, old value: 101μs.&lt;br&gt; New value: 43.6ms, old value: 37.2ms.&lt;br&gt;
Latest value: 116μs (15.75% worse Latest value: 43.6ms (31.10% worse
than best value 101μs).</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; 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; New value: 37.2ms, old value: 33.3ms.&lt;br&gt;
Latest value: 37.2ms (11.92% worse Latest value: 43.6ms (31.10% worse
than best value 33.3ms).</content></entry></feed> than best value 33.3ms).</content></entry></feed>

View file

@ -134,7 +134,7 @@ Which will render as:
{% endcomponent %} {% 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 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: the to `2024-12-14`, which is Saturday, our template from previous step would render this:
@ -289,3 +289,8 @@ each time:
Moreover, slots are treated as part of the template - for example the CSS scoping (work in progress) Moreover, slots are treated as part of the template - for example the CSS scoping (work in progress)
is applied to the slot content too. 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

@ -55,7 +55,7 @@ by calling `{% load component_tags %}` inside the template.
like `{% component "calendar" / %}`. like `{% component "calendar" / %}`.
`ComponentRegistries` also make it possible to group and share components as standalone packages. `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 !!! note

View file

@ -220,10 +220,6 @@ the parametrized version of the component:
</div> </div>
``` ```
---
Next, you will learn [how to use slots give your components even more flexibility ➡️](./adding_slots.md)
### 5. Add defaults ### 5. Add defaults
In our example, we've set the `extra_class` to default to `"text-blue"` by setting it in the In our example, we've set the `extra_class` to default to `"text-blue"` by setting it in the
@ -258,3 +254,7 @@ class Calendar(Component):
"extra_class": kwargs["extra_class"], # <--- changed "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

@ -160,7 +160,7 @@ def my_view(request):
response_class = MyCustomResponse response_class = MyCustomResponse
``` ```
### Rendering slots ### 4. Rendering slots
Slots content are automatically escaped by default to prevent XSS attacks. Slots content are automatically escaped by default to prevent XSS attacks.
@ -203,7 +203,7 @@ Calendar.render(
[`format_html`](https://docs.djangoproject.com/en/5.2/ref/utils/#django.utils.html.format_html) [`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). and [`mark_safe`](https://docs.djangoproject.com/en/5.2/ref/utils/#django.utils.safestring.mark_safe).
### Component views and URLs ### 5. Component views and URLs
For web applications, it's common to define endpoints that serve HTML content (AKA views). For web applications, it's common to define endpoints that serve HTML content (AKA views).

View file

@ -35,8 +35,9 @@ document.querySelector(".calendar").onclick = function () {
``` ```
```py title="calendar.py" ```py title="calendar.py"
from django_components import Component from django_components import Component, register
@register("calendar")
class Calendar(Component): class Calendar(Component):
template_file = "calendar.html" template_file = "calendar.html"
js_file = "calendar.js" js_file = "calendar.js"

View file

@ -7,9 +7,9 @@ Please, before opening a new discussion, [check if similar discussion wasn't ope
## Community examples ## Community examples
One of our goals with `django-components` is to make it easy to share components between projects One of our goals with `django-components` is to make it easy to share components between projects
([see how to package components](../concepts/advanced/authoring_component_libraries.md)). ([see how to package components](../concepts/advanced/component_libraries.md)).
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. 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/). - [djc-heroicons](https://pypi.org/project/djc-heroicons/): A component that renders icons from [Heroicons.com](https://heroicons.com/).

View file

@ -18,8 +18,9 @@ A component in django-components can be as simple as a Django template and Pytho
``` ```
```py title="components/calendar/calendar.py" ```py title="components/calendar/calendar.py"
from django_components import Component from django_components import Component, register
@register("calendar")
class Calendar(Component): class Calendar(Component):
template_file = "calendar.html" template_file = "calendar.html"
``` ```
@ -46,8 +47,9 @@ document.querySelector(".calendar").onclick = () => {
``` ```
```py title="components/calendar/calendar.py" ```py title="components/calendar/calendar.py"
from django_components import Component from django_components import Component, register
@register("calendar")
class Calendar(Component): class Calendar(Component):
template_file = "calendar.html" template_file = "calendar.html"
js_file = "calendar.js" js_file = "calendar.js"

View file

@ -1,16 +1,16 @@
# `.nav.yml` is provided by https://lukasgeiter.github.io/mkdocs-awesome-nav # `.nav.yml` is provided by https://lukasgeiter.github.io/mkdocs-awesome-nav
nav: nav:
- API: api.md - API: api.md
- Commands: commands.md - CLI commands: commands.md
- Components: components.md - Components: components.md
- Exceptions: exceptions.md - Exceptions: exceptions.md
- Extension commands: extension_commands.md
- Extension hooks: extension_hooks.md - Extension hooks: extension_hooks.md
- Extension URLs: extension_urls.md - Extension commands API: extension_commands.md
- Extension URLs API: extension_urls.md
- Settings: settings.md - Settings: settings.md
- Signals: signals.md - Signals: signals.md
- Tag formatters: tag_formatters.md - Tag formatters: tag_formatters.md
- Template tags: template_tags.md - Template tags: template_tags.md
- Template vars: template_vars.md - Template variables: template_variables.md
- URLs: urls.md - URLs: urls.md
- Testing API: testing_api.md - Testing API: testing_api.md

View file

@ -1,6 +1,6 @@
<!-- Autogenerated by reference.py --> <!-- Autogenerated by reference.py -->
# Extension commands # Extension commands API
Overview of all classes, functions, and other objects related to defining extension commands. Overview of all classes, functions, and other objects related to defining extension commands.

View file

@ -148,6 +148,42 @@ name | type | description
`name` | `str` | The name the component was registered under `name` | `str` | The name the component was registered under
`registry` | [`ComponentRegistry`](../api#django_components.ComponentRegistry) | The registry the component was unregistered from `registry` | [`ComponentRegistry`](../api#django_components.ComponentRegistry) | The registry the component was unregistered from
::: django_components.extension.ComponentExtension.on_css_loaded
options:
heading_level: 3
show_root_heading: true
show_signature: true
separate_signature: true
show_symbol_type_heading: false
show_symbol_type_toc: false
show_if_no_docstring: true
show_labels: false
**Available data:**
name | type | description
--|--|--
`component_cls` | [`Type[Component]`](../api#django_components.Component) | The Component class whose CSS was loaded
`content` | `str` | The CSS content (string)
::: django_components.extension.ComponentExtension.on_js_loaded
options:
heading_level: 3
show_root_heading: true
show_signature: true
separate_signature: true
show_symbol_type_heading: false
show_symbol_type_toc: false
show_if_no_docstring: true
show_labels: false
**Available data:**
name | type | description
--|--|--
`component_cls` | [`Type[Component]`](../api#django_components.Component) | The Component class whose JS was loaded
`content` | `str` | The JS content (string)
::: django_components.extension.ComponentExtension.on_registry_created ::: django_components.extension.ComponentExtension.on_registry_created
options: options:
heading_level: 3 heading_level: 3
@ -207,6 +243,44 @@ name | type | description
`slot_name` | `str` | The name of the `{% slot %}` tag `slot_name` | `str` | The name of the `{% slot %}` tag
`slot_node` | `SlotNode` | The node instance of the `{% slot %}` tag `slot_node` | `SlotNode` | The node instance of the `{% slot %}` tag
::: django_components.extension.ComponentExtension.on_template_compiled
options:
heading_level: 3
show_root_heading: true
show_signature: true
separate_signature: true
show_symbol_type_heading: false
show_symbol_type_toc: false
show_if_no_docstring: true
show_labels: false
**Available data:**
name | type | description
--|--|--
`component_cls` | [`Type[Component]`](../api#django_components.Component) | The Component class whose template was loaded
`template` | `django.template.base.Template` | The compiled template object
::: django_components.extension.ComponentExtension.on_template_loaded
options:
heading_level: 3
show_root_heading: true
show_signature: true
separate_signature: true
show_symbol_type_heading: false
show_symbol_type_toc: false
show_if_no_docstring: true
show_labels: false
**Available data:**
name | type | description
--|--|--
`component_cls` | [`Type[Component]`](../api#django_components.Component) | The Component class whose template was loaded
`content` | `str` | The template string
`name` | `Optional[str]` | The name of the template
`origin` | `Optional[django.template.base.Origin]` | The origin of the template
## Objects ## Objects
::: django_components.extension.OnComponentClassCreatedContext ::: django_components.extension.OnComponentClassCreatedContext

View file

@ -1,6 +1,6 @@
<!-- Autogenerated by reference.py --> <!-- Autogenerated by reference.py -->
# Extension URLs # Extension URLs API
Overview of all classes, functions, and other objects related to defining extension URLs. Overview of all classes, functions, and other objects related to defining extension URLs.

View file

@ -67,7 +67,7 @@ If you insert this tag multiple times, ALL JS scripts will be duplicately insert
<a href="https://github.com/django-components/django-components/tree/master/src/django_components/templatetags/component_tags.py#L3796" target="_blank">See source code</a> <a href="https://github.com/django-components/django-components/tree/master/src/django_components/templatetags/component_tags.py#L3799" target="_blank">See source code</a>

View file

@ -44,6 +44,7 @@ from pathlib import Path
from textwrap import dedent from textwrap import dedent
from typing import Any, Dict, List, NamedTuple, Optional, Sequence, Tuple, Type, Union from typing import Any, Dict, List, NamedTuple, Optional, Sequence, Tuple, Type, Union
from django.conf import settings
from django.core.management.base import BaseCommand from django.core.management.base import BaseCommand
from django.urls import URLPattern, URLResolver from django.urls import URLPattern, URLResolver
@ -465,7 +466,13 @@ def gen_reference_commands():
# Add link to source code # Add link to source code
module_abs_path = import_module(cmd_def_cls.__module__).__file__ module_abs_path = import_module(cmd_def_cls.__module__).__file__
module_rel_path = Path(module_abs_path).relative_to(Path.cwd()).as_posix() # type: ignore[arg-type] module_rel_path = Path(module_abs_path).relative_to(Path.cwd()).as_posix() # type: ignore[arg-type]
# NOTE: Raises `OSError` if the file is not found.
try:
obj_lineno = inspect.findsource(cmd_def_cls)[1] obj_lineno = inspect.findsource(cmd_def_cls)[1]
except Exception:
obj_lineno = None
source_code_link = _format_source_code_html(module_rel_path, obj_lineno) source_code_link = _format_source_code_html(module_rel_path, obj_lineno)
# NOTE: For the commands we have to generate the markdown entries ourselves, # NOTE: For the commands we have to generate the markdown entries ourselves,
@ -524,7 +531,7 @@ def gen_reference_commands():
) )
def gen_reference_templatetags(): def gen_reference_template_tags():
""" """
Generate documentation for all Django template tags defined by django-components, Generate documentation for all Django template tags defined by django-components,
like `{% slot %}`, `{% component %}`, etc. like `{% slot %}`, `{% component %}`, etc.
@ -537,7 +544,7 @@ def gen_reference_templatetags():
] ]
preface = "<!-- Autogenerated by reference.py -->\n\n" preface = "<!-- Autogenerated by reference.py -->\n\n"
preface += (root / "docs/templates/reference_templatetags.md").read_text() preface += (root / "docs/templates/reference_template_tags.md").read_text()
out_file = root / "docs/reference/template_tags.md" out_file = root / "docs/reference/template_tags.md"
out_file.parent.mkdir(parents=True, exist_ok=True) out_file.parent.mkdir(parents=True, exist_ok=True)
@ -585,14 +592,14 @@ def gen_reference_templatetags():
) )
def gen_reference_templatevars(): def gen_reference_template_variables():
""" """
Generate documentation for all variables that are available inside the component templates Generate documentation for all variables that are available inside the component templates
under the `{{ component_vars }}` variable, as defined by `ComponentVars`. under the `{{ component_vars }}` variable, as defined by `ComponentVars`.
""" """
preface = "<!-- Autogenerated by reference.py -->\n\n" preface = "<!-- Autogenerated by reference.py -->\n\n"
preface += (root / "docs/templates/reference_templatevars.md").read_text() preface += (root / "docs/templates/reference_template_variables.md").read_text()
out_file = root / "docs/reference/template_vars.md" out_file = root / "docs/reference/template_variables.md"
out_file.parent.mkdir(parents=True, exist_ok=True) out_file.parent.mkdir(parents=True, exist_ok=True)
with out_file.open("w", encoding="utf-8") as f: with out_file.open("w", encoding="utf-8") as f:
@ -1099,6 +1106,13 @@ def _is_extension_url_api(obj: Any) -> bool:
def gen_reference(): def gen_reference():
"""The entrypoint to generate all the reference documentation.""" """The entrypoint to generate all the reference documentation."""
# Set up Django settings so we can import `extensions`
if not settings.configured:
settings.configure(
BASE_DIR=Path(__file__).parent.parent.parent,
)
gen_reference_api() gen_reference_api()
gen_reference_exceptions() gen_reference_exceptions()
gen_reference_components() gen_reference_components()
@ -1106,8 +1120,8 @@ def gen_reference():
gen_reference_tagformatters() gen_reference_tagformatters()
gen_reference_urls() gen_reference_urls()
gen_reference_commands() gen_reference_commands()
gen_reference_templatetags() gen_reference_template_tags()
gen_reference_templatevars() gen_reference_template_variables()
gen_reference_signals() gen_reference_signals()
gen_reference_testing_api() gen_reference_testing_api()
gen_reference_extension_hooks() gen_reference_extension_hooks()

View file

@ -1,4 +1,4 @@
# Extension commands # Extension commands API
Overview of all classes, functions, and other objects related to defining extension commands. Overview of all classes, functions, and other objects related to defining extension commands.

View file

@ -1,4 +1,4 @@
# Extension URLs # Extension URLs API
Overview of all classes, functions, and other objects related to defining extension URLs. Overview of all classes, functions, and other objects related to defining extension URLs.

View file

@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project] [project]
name = "django_components" name = "django_components"
version = "0.140.0" version = "0.141.2"
requires-python = ">=3.8, <4.0" requires-python = ">=3.8, <4.0"
description = "A way to create simple reusable template components in Django." description = "A way to create simple reusable template components in Django."
keywords = ["django", "components", "css", "js", "html"] keywords = ["django", "components", "css", "js", "html"]

View file

@ -7,7 +7,7 @@ whitenoise
asv asv
# NOTE: pin virtualenv to <20.31 until asv fixes integration # NOTE: pin virtualenv to <20.31 until asv fixes integration
# See https://github.com/airspeed-velocity/asv/issues/1484 # See https://github.com/airspeed-velocity/asv/issues/1484
virtualenv==20.31.2 virtualenv==20.32.0
pytest-asyncio pytest-asyncio
pytest-django pytest-django
typing-extensions>=4.12.2 typing-extensions>=4.12.2

View file

@ -104,7 +104,7 @@ urllib3==2.2.3
# via # via
# requests # requests
# types-requests # types-requests
virtualenv==20.31.2 virtualenv==20.32.0
# via # via
# -r requirements-ci.in # -r requirements-ci.in
# asv # asv

View file

@ -20,6 +20,6 @@ pygments-djc
asv asv
# NOTE: pin virtualenv to <20.31 until asv fixes integration # NOTE: pin virtualenv to <20.31 until asv fixes integration
# See https://github.com/airspeed-velocity/asv/issues/1484 # See https://github.com/airspeed-velocity/asv/issues/1484
virtualenv==20.31.2 virtualenv==20.32.0
typing-extensions>=4.12.2 typing-extensions>=4.12.2
pathspec pathspec

View file

@ -30,7 +30,7 @@ colorama==0.4.6
# via tox # via tox
distlib==0.3.9 distlib==0.3.9
# via virtualenv # via virtualenv
django==4.2.21 django==4.2.23
# via -r requirements-dev.in # via -r requirements-dev.in
djc-core-html-parser==1.0.2 djc-core-html-parser==1.0.2
# via -r requirements-dev.in # via -r requirements-dev.in
@ -40,7 +40,7 @@ filelock==3.16.1
# via # via
# tox # tox
# virtualenv # virtualenv
flake8==7.2.0 flake8==7.3.0
# via # via
# -r requirements-dev.in # -r requirements-dev.in
# flake8-pyproject # flake8-pyproject
@ -64,7 +64,7 @@ json5==0.10.0
# via asv # via asv
mccabe==0.7.0 mccabe==0.7.0
# via flake8 # via flake8
mypy==1.16.0 mypy==1.17.0
# via -r requirements-dev.in # via -r requirements-dev.in
mypy-extensions==1.0.0 mypy-extensions==1.0.0
# via # via
@ -97,11 +97,11 @@ pluggy==1.5.0
# tox # tox
pre-commit==4.2.0 pre-commit==4.2.0
# via -r requirements-dev.in # via -r requirements-dev.in
pycodestyle==2.13.0 pycodestyle==2.14.0
# via flake8 # via flake8
pyee==12.0.0 pyee==12.0.0
# via playwright # via playwright
pyflakes==3.3.2 pyflakes==3.4.0
# via flake8 # via flake8
pygments==2.19.1 pygments==2.19.1
# via # via
@ -163,7 +163,7 @@ urllib3==2.2.3
# via # via
# requests # requests
# types-requests # types-requests
virtualenv==20.31.2 virtualenv==20.32.0
# via # via
# -r requirements-dev.in # -r requirements-dev.in
# asv # asv

View file

@ -24,7 +24,7 @@
# - djc-core-html-parser>=1.0 # - djc-core-html-parser>=1.0
# #
asgiref==3.8.1 asgiref==3.9.1
# via django # via django
babel==2.17.0 babel==2.17.0
# via # via
@ -32,13 +32,13 @@ babel==2.17.0
# mkdocs-material # mkdocs-material
black==25.1.0 black==25.1.0
# via hatch.envs.docs # via hatch.envs.docs
bracex==2.5.post1 bracex==2.6
# via wcmatch # via wcmatch
cairocffi==1.7.1 cairocffi==1.7.1
# via cairosvg # via cairosvg
cairosvg==2.8.2 cairosvg==2.8.2
# via mkdocs-material # via mkdocs-material
certifi==2025.4.26 certifi==2025.7.14
# via requests # via requests
cffi==1.17.1 cffi==1.17.1
# via cairocffi # via cairocffi
@ -58,7 +58,7 @@ cssselect2==0.8.0
# via cairosvg # via cairosvg
defusedxml==0.7.1 defusedxml==0.7.1
# via cairosvg # via cairosvg
django==4.2.21 django==4.2.23
# via hatch.envs.docs # via hatch.envs.docs
djc-core-html-parser==1.0.2 djc-core-html-parser==1.0.2
# via hatch.envs.docs # via hatch.envs.docs
@ -66,9 +66,9 @@ ghp-import==2.1.0
# via mkdocs # via mkdocs
gitdb==4.0.12 gitdb==4.0.12
# via gitpython # via gitpython
gitpython==3.1.44 gitpython==3.1.45
# via mkdocs-git-revision-date-localized-plugin # via mkdocs-git-revision-date-localized-plugin
griffe==1.7.3 griffe==1.9.0
# via mkdocstrings-python # via mkdocstrings-python
htmlmin2==0.1.13 htmlmin2==0.1.13
# via mkdocs-minify-plugin # via mkdocs-minify-plugin
@ -86,14 +86,14 @@ jinja2==3.1.6
# mkdocstrings # mkdocstrings
jsmin==3.0.1 jsmin==3.0.1
# via mkdocs-minify-plugin # via mkdocs-minify-plugin
markdown==3.8 markdown==3.8.2
# via # via
# mkdocs # mkdocs
# mkdocs-autorefs # mkdocs-autorefs
# mkdocs-material # mkdocs-material
# mkdocstrings # mkdocstrings
# pymdown-extensions # pymdown-extensions
markdown-exec==1.10.3 markdown-exec==1.11.0
# via hatch.envs.docs # via hatch.envs.docs
markupsafe==3.0.2 markupsafe==3.0.2
# via # via
@ -134,13 +134,13 @@ mkdocs-get-deps==0.2.0
# via # via
# mkdocs # mkdocs
# mkdocstrings # mkdocstrings
mkdocs-git-authors-plugin==0.9.5 mkdocs-git-authors-plugin==0.10.0
# via hatch.envs.docs # via hatch.envs.docs
mkdocs-git-revision-date-localized-plugin==1.4.7 mkdocs-git-revision-date-localized-plugin==1.4.7
# via hatch.envs.docs # via hatch.envs.docs
mkdocs-include-markdown-plugin==7.1.5 mkdocs-include-markdown-plugin==7.1.6
# via hatch.envs.docs # via hatch.envs.docs
mkdocs-material==9.6.14 mkdocs-material==9.6.15
# via hatch.envs.docs # via hatch.envs.docs
mkdocs-material-extensions==1.3.1 mkdocs-material-extensions==1.3.1
# via mkdocs-material # via mkdocs-material
@ -148,11 +148,11 @@ mkdocs-minify-plugin==0.8.0
# via hatch.envs.docs # via hatch.envs.docs
mkdocs-redirects==1.2.2 mkdocs-redirects==1.2.2
# via hatch.envs.docs # via hatch.envs.docs
mkdocstrings==0.29.1 mkdocstrings==0.30.0
# via # via
# hatch.envs.docs # hatch.envs.docs
# mkdocstrings-python # mkdocstrings-python
mkdocstrings-python==1.16.11 mkdocstrings-python==1.16.12
# via hatch.envs.docs # via hatch.envs.docs
mypy-extensions==1.1.0 mypy-extensions==1.1.0
# via black # via black
@ -166,7 +166,7 @@ pathspec==0.12.1
# via # via
# black # black
# mkdocs # mkdocs
pillow==11.2.1 pillow==11.3.0
# via # via
# cairosvg # cairosvg
# mkdocs-material # mkdocs-material
@ -176,14 +176,14 @@ platformdirs==4.3.8
# mkdocs-get-deps # mkdocs-get-deps
pycparser==2.22 pycparser==2.22
# via cffi # via cffi
pygments==2.19.1 pygments==2.19.2
# via # via
# hatch.envs.docs # hatch.envs.docs
# mkdocs-material # mkdocs-material
# pygments-djc # pygments-djc
pygments-djc==1.0.1 pygments-djc==1.0.1
# via hatch.envs.docs # via hatch.envs.docs
pymdown-extensions==10.15 pymdown-extensions==10.16.1
# via # via
# hatch.envs.docs # hatch.envs.docs
# markdown-exec # markdown-exec
@ -208,7 +208,7 @@ pyyaml-env-tag==1.1
# mkdocs # mkdocs
regex==2024.11.6 regex==2024.11.6
# via mkdocs-material # via mkdocs-material
requests==2.32.3 requests==2.32.4
# via mkdocs-material # via mkdocs-material
six==1.17.0 six==1.17.0
# via python-dateutil # via python-dateutil
@ -220,17 +220,17 @@ tinycss2==1.4.0
# via # via
# cairosvg # cairosvg
# cssselect2 # cssselect2
urllib3==2.4.0 urllib3==2.5.0
# via requests # via requests
verspec==0.1.0 verspec==0.1.0
# via mike # via mike
watchdog==6.0.0 watchdog==6.0.0
# via mkdocs # via mkdocs
wcmatch==10.0 wcmatch==10.1
# via mkdocs-include-markdown-plugin # via mkdocs-include-markdown-plugin
webencodings==0.5.1 webencodings==0.5.1
# via # via
# cssselect2 # cssselect2
# tinycss2 # tinycss2
zipp==3.22.0 zipp==3.23.0
# via importlib-metadata # via importlib-metadata

View file

@ -746,8 +746,8 @@ defaults = ComponentsSettings(
# #
# Settings are loaded from Django settings only once, at `apps.py` in `ready()`. # Settings are loaded from Django settings only once, at `apps.py` in `ready()`.
class InternalSettings: class InternalSettings:
def __init__(self, settings: Optional[Dict[str, Any]] = None): def __init__(self) -> None:
self._settings = ComponentsSettings(**settings) if settings else defaults self._settings: Optional[ComponentsSettings] = None
def _load_settings(self) -> None: def _load_settings(self) -> None:
data = getattr(settings, "COMPONENTS", {}) data = getattr(settings, "COMPONENTS", {})
@ -786,6 +786,11 @@ class InternalSettings:
tag_formatter=default(components_settings.tag_formatter, defaults.tag_formatter), # type: ignore[arg-type] tag_formatter=default(components_settings.tag_formatter, defaults.tag_formatter), # type: ignore[arg-type]
) )
def _get_settings(self) -> ComponentsSettings:
if self._settings is None:
self._load_settings()
return cast(ComponentsSettings, self._settings)
def _prepare_extensions(self, new_settings: ComponentsSettings) -> List["ComponentExtension"]: def _prepare_extensions(self, new_settings: ComponentsSettings) -> List["ComponentExtension"]:
extensions: Sequence[Union[Type["ComponentExtension"], str]] = default( extensions: Sequence[Union[Type["ComponentExtension"], str]] = default(
new_settings.extensions, cast(List[str], defaults.extensions) new_settings.extensions, cast(List[str], defaults.extensions)
@ -795,6 +800,7 @@ class InternalSettings:
from django_components.extensions.cache import CacheExtension from django_components.extensions.cache import CacheExtension
from django_components.extensions.debug_highlight import DebugHighlightExtension from django_components.extensions.debug_highlight import DebugHighlightExtension
from django_components.extensions.defaults import DefaultsExtension from django_components.extensions.defaults import DefaultsExtension
from django_components.extensions.dependencies import DependenciesExtension
from django_components.extensions.view import ViewExtension from django_components.extensions.view import ViewExtension
extensions = cast( extensions = cast(
@ -802,6 +808,7 @@ class InternalSettings:
[ [
CacheExtension, CacheExtension,
DefaultsExtension, DefaultsExtension,
DependenciesExtension,
ViewExtension, ViewExtension,
DebugHighlightExtension, DebugHighlightExtension,
], ],
@ -853,70 +860,73 @@ class InternalSettings:
return raw_value return raw_value
# TODO REMOVE THE PROPERTIES BELOW? THEY NO LONGER SERVE ANY PURPOSE
@property @property
def AUTODISCOVER(self) -> bool: def AUTODISCOVER(self) -> bool:
return self._settings.autodiscover # type: ignore[return-value] return self._get_settings().autodiscover # type: ignore[return-value]
@property @property
def CACHE(self) -> Optional[str]: def CACHE(self) -> Optional[str]:
return self._settings.cache return self._get_settings().cache
@property @property
def DIRS(self) -> Sequence[Union[str, PathLike, Tuple[str, str], Tuple[str, PathLike]]]: def DIRS(self) -> Sequence[Union[str, PathLike, Tuple[str, str], Tuple[str, PathLike]]]:
return self._settings.dirs # type: ignore[return-value] return self._get_settings().dirs # type: ignore[return-value]
@property @property
def APP_DIRS(self) -> Sequence[str]: def APP_DIRS(self) -> Sequence[str]:
return self._settings.app_dirs # type: ignore[return-value] return self._get_settings().app_dirs # type: ignore[return-value]
@property @property
def DEBUG_HIGHLIGHT_COMPONENTS(self) -> bool: def DEBUG_HIGHLIGHT_COMPONENTS(self) -> bool:
return self._settings.debug_highlight_components # type: ignore[return-value] return self._get_settings().debug_highlight_components # type: ignore[return-value]
@property @property
def DEBUG_HIGHLIGHT_SLOTS(self) -> bool: def DEBUG_HIGHLIGHT_SLOTS(self) -> bool:
return self._settings.debug_highlight_slots # type: ignore[return-value] return self._get_settings().debug_highlight_slots # type: ignore[return-value]
@property @property
def DYNAMIC_COMPONENT_NAME(self) -> str: def DYNAMIC_COMPONENT_NAME(self) -> str:
return self._settings.dynamic_component_name # type: ignore[return-value] return self._get_settings().dynamic_component_name # type: ignore[return-value]
@property @property
def LIBRARIES(self) -> List[str]: def LIBRARIES(self) -> List[str]:
return self._settings.libraries # type: ignore[return-value] return self._get_settings().libraries # type: ignore[return-value]
@property @property
def EXTENSIONS(self) -> List["ComponentExtension"]: def EXTENSIONS(self) -> List["ComponentExtension"]:
return self._settings.extensions # type: ignore[return-value] return self._get_settings().extensions # type: ignore[return-value]
@property
def EXTENSIONS_DEFAULTS(self) -> Dict[str, Any]:
return self._get_settings().extensions_defaults # type: ignore[return-value]
@property @property
def MULTILINE_TAGS(self) -> bool: def MULTILINE_TAGS(self) -> bool:
return self._settings.multiline_tags # type: ignore[return-value] return self._get_settings().multiline_tags # type: ignore[return-value]
@property @property
def RELOAD_ON_FILE_CHANGE(self) -> bool: def RELOAD_ON_FILE_CHANGE(self) -> bool:
return self._settings.reload_on_file_change # type: ignore[return-value] return self._get_settings().reload_on_file_change # type: ignore[return-value]
@property @property
def TEMPLATE_CACHE_SIZE(self) -> int: def TEMPLATE_CACHE_SIZE(self) -> int:
return self._settings.template_cache_size # type: ignore[return-value] return self._get_settings().template_cache_size # type: ignore[return-value]
@property @property
def STATIC_FILES_ALLOWED(self) -> Sequence[Union[str, re.Pattern]]: def STATIC_FILES_ALLOWED(self) -> Sequence[Union[str, re.Pattern]]:
return self._settings.static_files_allowed # type: ignore[return-value] return self._get_settings().static_files_allowed # type: ignore[return-value]
@property @property
def STATIC_FILES_FORBIDDEN(self) -> Sequence[Union[str, re.Pattern]]: def STATIC_FILES_FORBIDDEN(self) -> Sequence[Union[str, re.Pattern]]:
return self._settings.static_files_forbidden # type: ignore[return-value] return self._get_settings().static_files_forbidden # type: ignore[return-value]
@property @property
def CONTEXT_BEHAVIOR(self) -> ContextBehavior: def CONTEXT_BEHAVIOR(self) -> ContextBehavior:
return ContextBehavior(self._settings.context_behavior) return ContextBehavior(self._get_settings().context_behavior)
@property @property
def TAG_FORMATTER(self) -> Union["TagFormatterABC", str]: def TAG_FORMATTER(self) -> Union["TagFormatterABC", str]:
return self._settings.tag_formatter # type: ignore[return-value] return self._get_settings().tag_formatter # type: ignore[return-value]
app_settings = InternalSettings() app_settings = InternalSettings()

View file

@ -4,6 +4,7 @@ from typing import Any
from django.apps import AppConfig from django.apps import AppConfig
from django.template import Template from django.template import Template
from django.template.loader_tags import IncludeNode
from django.utils.autoreload import file_changed, trigger_reload from django.utils.autoreload import file_changed, trigger_reload
@ -18,14 +19,13 @@ class ComponentsConfig(AppConfig):
from django_components.component_registry import registry from django_components.component_registry import registry
from django_components.components.dynamic import DynamicComponent from django_components.components.dynamic import DynamicComponent
from django_components.extension import extensions from django_components.extension import extensions
from django_components.util.django_monkeypatch import monkeypatch_template_cls from django_components.util.django_monkeypatch import monkeypatch_include_node, monkeypatch_template_cls
app_settings._load_settings()
# NOTE: This monkeypatch is applied here, before Django processes any requests. # NOTE: This monkeypatch is applied here, before Django processes any requests.
# To make django-components work with django-debug-toolbar-template-profiler # To make django-components work with django-debug-toolbar-template-profiler
# See https://github.com/django-components/django-components/discussions/819 # See https://github.com/django-components/django-components/discussions/819
monkeypatch_template_cls(Template) monkeypatch_template_cls(Template)
monkeypatch_include_node(IncludeNode)
# Import modules set in `COMPONENTS.libraries` setting # Import modules set in `COMPONENTS.libraries` setting
import_libraries() import_libraries()

View file

@ -1,3 +1,4 @@
import sys
from typing import Optional from typing import Optional
from django.core.cache import BaseCache, caches from django.core.cache import BaseCache, caches
@ -36,9 +37,14 @@ def get_component_media_cache() -> BaseCache:
component_media_cache = LocMemCache( component_media_cache = LocMemCache(
"django-components-media", "django-components-media",
{ {
"TIMEOUT": None, # No timeout # No max size nor timeout
"MAX_ENTRIES": None, # No max size # NOTE: Implementation of `BaseCache` coerces the `MAX_ENTRIES` value
"CULL_FREQUENCY": 3, # to `int()` so we use exact max size instead of `inf` or `None`.
# See https://github.com/django/django/blob/94ebcf8366d62f6360851b40e9c4dfe3f71d202f/django/core/cache/backends/base.py#L73 # noqa: E501
"TIMEOUT": None,
"OPTIONS": {
"MAX_ENTRIES": sys.maxsize,
},
}, },
) )

View file

@ -19,8 +19,11 @@ from django_components.util.command import ComponentCommand
def _gen_subcommands() -> List[Type[ComponentCommand]]: def _gen_subcommands() -> List[Type[ComponentCommand]]:
commands: List[Type[ComponentCommand]] = [] commands: List[Type[ComponentCommand]] = []
for extension in extensions.extensions: for extension in extensions.extensions:
if not extension.commands:
continue
ExtCommand = type( ExtCommand = type(
"ExtCommand", "ExtRunSubcommand_" + extension.name,
(ComponentCommand,), (ComponentCommand,),
{ {
"name": extension.name, "name": extension.name,

View file

@ -32,7 +32,7 @@ from django_components.component_media import ComponentMediaInput, ComponentMedi
from django_components.component_registry import ComponentRegistry from django_components.component_registry import ComponentRegistry
from django_components.component_registry import registry as registry_ from django_components.component_registry import registry as registry_
from django_components.constants import COMP_ID_PREFIX from django_components.constants import COMP_ID_PREFIX
from django_components.context import _COMPONENT_CONTEXT_KEY, make_isolated_context_copy from django_components.context import _COMPONENT_CONTEXT_KEY, COMPONENT_IS_NESTED_KEY, make_isolated_context_copy
from django_components.dependencies import ( from django_components.dependencies import (
DependenciesStrategy, DependenciesStrategy,
cache_component_css, cache_component_css,
@ -2277,7 +2277,7 @@ class Component(metaclass=ComponentMeta):
deps_strategy = cast(DependenciesStrategy, default(deps_strategy, "document")) deps_strategy = cast(DependenciesStrategy, default(deps_strategy, "document"))
self.id = default(id, _gen_component_id, factory=True) self.id = default(id, _gen_component_id, factory=True) # type: ignore[arg-type]
self.name = _get_component_name(self.__class__, registered_name) self.name = _get_component_name(self.__class__, registered_name)
self.registered_name: Optional[str] = registered_name self.registered_name: Optional[str] = registered_name
self.args = default(args, []) self.args = default(args, [])
@ -2314,9 +2314,6 @@ class Component(metaclass=ComponentMeta):
cls.class_id = hash_comp_cls(cls) cls.class_id = hash_comp_cls(cls)
comp_cls_id_mapping[cls.class_id] = cls comp_cls_id_mapping[cls.class_id] = cls
# Make sure that subclassed component will store it's own template, not the parent's.
cls._template = None
ALL_COMPONENTS.append(cached_ref(cls)) # type: ignore[arg-type] ALL_COMPONENTS.append(cached_ref(cls)) # type: ignore[arg-type]
extensions._init_component_class(cls) extensions._init_component_class(cls)
extensions.on_component_class_created(OnComponentClassCreatedContext(cls)) extensions.on_component_class_created(OnComponentClassCreatedContext(cls))
@ -3489,11 +3486,12 @@ class Component(metaclass=ComponentMeta):
) )
) )
# Process Component's JS and CSS # Cache component's JS and CSS scripts, in case they have been evicted from the cache.
cache_component_js(comp_cls) cache_component_js(comp_cls, force=False)
js_input_hash = cache_component_js_vars(comp_cls, js_data) if js_data else None cache_component_css(comp_cls, force=False)
cache_component_css(comp_cls) # Create JS/CSS scripts that will load the JS/CSS variables into the page.
js_input_hash = cache_component_js_vars(comp_cls, js_data) if js_data else None
css_input_hash = cache_component_css_vars(comp_cls, css_data) if css_data else None css_input_hash = cache_component_css_vars(comp_cls, css_data) if css_data else None
############################################################################# #############################################################################
@ -3517,14 +3515,14 @@ class Component(metaclass=ComponentMeta):
# Then we can simply apply `template_data` to the context in the same layer # Then we can simply apply `template_data` to the context in the same layer
# where we apply `context_processor_data` and `component_vars`. # where we apply `context_processor_data` and `component_vars`.
with prepare_component_template(component, template_data) as template: with prepare_component_template(component, template_data) as template:
# Set `Template._djc_is_component_nested` based on whether we're currently INSIDE # Set `_DJC_COMPONENT_IS_NESTED` based on whether we're currently INSIDE
# the `{% extends %}` tag. # the `{% extends %}` tag.
# Part of fix for https://github.com/django-components/django-components/issues/508 # Part of fix for https://github.com/django-components/django-components/issues/508
# See django_monkeypatch.py # See django_monkeypatch.py
if template is not None: if template is not None:
template._djc_is_component_nested = bool( comp_is_nested = bool(context.render_context.get(BLOCK_CONTEXT_KEY)) # type: ignore[union-attr]
context.render_context.get(BLOCK_CONTEXT_KEY) # type: ignore[union-attr] else:
) comp_is_nested = False
# Capture the template name so we can print better error messages (currently used in slots) # Capture the template name so we can print better error messages (currently used in slots)
component_ctx.template_name = template.name if template else None component_ctx.template_name = template.name if template else None
@ -3535,6 +3533,7 @@ class Component(metaclass=ComponentMeta):
**component.context_processors_data, **component.context_processors_data,
# Private context fields # Private context fields
_COMPONENT_CONTEXT_KEY: render_id, _COMPONENT_CONTEXT_KEY: render_id,
COMPONENT_IS_NESTED_KEY: comp_is_nested,
# NOTE: Public API for variables accessible from within a component's template # NOTE: Public API for variables accessible from within a component's template
# See https://github.com/django-components/django-components/issues/280#issuecomment-2081180940 # See https://github.com/django-components/django-components/issues/280#issuecomment-2081180940
"component_vars": ComponentVars( "component_vars": ComponentVars(
@ -3672,7 +3671,7 @@ class Component(metaclass=ComponentMeta):
# ``` # ```
def _gen_component_renderer( def _gen_component_renderer(
self, self,
template: Template, template: Optional[Template],
context: Context, context: Context,
component_path: List[str], component_path: List[str],
css_input_hash: Optional[str], css_input_hash: Optional[str],
@ -3697,6 +3696,7 @@ class Component(metaclass=ComponentMeta):
component.on_render_before(context, template) component.on_render_before(context, template)
# Emit signal that the template is about to be rendered # Emit signal that the template is about to be rendered
if template is not None:
template_rendered.send(sender=template, template=template, context=context) template_rendered.send(sender=template, template=template, context=context)
# Get the component's HTML # Get the component's HTML

View file

@ -27,10 +27,12 @@ from weakref import WeakKeyDictionary
from django.contrib.staticfiles import finders from django.contrib.staticfiles import finders
from django.core.exceptions import ImproperlyConfigured from django.core.exceptions import ImproperlyConfigured
from django.forms.widgets import Media as MediaCls from django.forms.widgets import Media as MediaCls
from django.template import Template
from django.utils.safestring import SafeData from django.utils.safestring import SafeData
from typing_extensions import TypeGuard from typing_extensions import TypeGuard
from django_components.template import load_component_template from django_components.extension import OnCssLoadedContext, OnJsLoadedContext, extensions
from django_components.template import ensure_unique_template, load_component_template
from django_components.util.loader import get_component_dirs, resolve_file from django_components.util.loader import get_component_dirs, resolve_file
from django_components.util.logger import logger from django_components.util.logger import logger
from django_components.util.misc import flatten, get_import_path, get_module_info, is_glob from django_components.util.misc import flatten, get_import_path, get_module_info, is_glob
@ -43,7 +45,7 @@ T = TypeVar("T")
# These are all the attributes that are handled by ComponentMedia and lazily-resolved # These are all the attributes that are handled by ComponentMedia and lazily-resolved
COMP_MEDIA_LAZY_ATTRS = ("media", "template", "template_file", "js", "js_file", "css", "css_file") COMP_MEDIA_LAZY_ATTRS = ("media", "template", "template_file", "js", "js_file", "css", "css_file", "_template")
# Sentinel value to indicate that a media attribute is not set. # Sentinel value to indicate that a media attribute is not set.
@ -267,6 +269,8 @@ class ComponentMedia:
js_file: Union[str, Unset, None] = UNSET js_file: Union[str, Unset, None] = UNSET
css: Union[str, Unset, None] = UNSET css: Union[str, Unset, None] = UNSET
css_file: Union[str, Unset, None] = UNSET css_file: Union[str, Unset, None] = UNSET
# Template instance that was loaded for this component
_template: Union[Template, Unset, None] = UNSET
def __post_init__(self) -> None: def __post_init__(self) -> None:
for inlined_attr in ("template", "js", "css"): for inlined_attr in ("template", "js", "css"):
@ -299,6 +303,7 @@ class ComponentMedia:
def reset(self) -> None: def reset(self) -> None:
self.__dict__.update(self._original.__dict__) self.__dict__.update(self._original.__dict__)
self.resolved = False self.resolved = False
self.resolved_relative_files = False
# This metaclass is all about one thing - lazily resolving the media files. # This metaclass is all about one thing - lazily resolving the media files.
@ -487,6 +492,10 @@ def _get_comp_cls_media(comp_cls: Type["Component"]) -> Any:
if curr_cls in media_cache: if curr_cls in media_cache:
continue continue
comp_media: Optional[ComponentMedia] = getattr(curr_cls, "_component_media", None)
if comp_media is not None and not comp_media.resolved:
_resolve_media(curr_cls, comp_media)
# Prepare base classes # Prepare base classes
# NOTE: If the `Component.Media` class is explicitly set to `None`, then we should not inherit # NOTE: If the `Component.Media` class is explicitly set to `None`, then we should not inherit
# from any parent classes. # from any parent classes.
@ -611,20 +620,39 @@ def _resolve_media(comp_cls: Type["Component"], comp_media: ComponentMedia) -> N
# Effectively, even if the Component class defined `js_file` (or others), at "runtime" the `js` attribute # Effectively, even if the Component class defined `js_file` (or others), at "runtime" the `js` attribute
# will be set to the content of the file. # will be set to the content of the file.
# So users can access `Component.js` even if they defined `Component.js_file`. # So users can access `Component.js` even if they defined `Component.js_file`.
comp_media.template = _get_asset( template_str, template_obj = _get_asset(
comp_cls, comp_cls,
comp_media, comp_media,
inlined_attr="template", inlined_attr="template",
file_attr="template_file", file_attr="template_file",
comp_dirs=comp_dirs, comp_dirs=comp_dirs,
type="template",
)
comp_media.js = _get_asset(
comp_cls, comp_media, inlined_attr="js", file_attr="js_file", comp_dirs=comp_dirs, type="static"
)
comp_media.css = _get_asset(
comp_cls, comp_media, inlined_attr="css", file_attr="css_file", comp_dirs=comp_dirs, type="static"
) )
comp_media.template = template_str
js_str, _ = _get_asset(comp_cls, comp_media, inlined_attr="js", file_attr="js_file", comp_dirs=comp_dirs)
comp_media.js = js_str
css_str, _ = _get_asset(comp_cls, comp_media, inlined_attr="css", file_attr="css_file", comp_dirs=comp_dirs)
comp_media.css = css_str
# If `Component.template` or `Component.template_file` were explicitly set on this class,
# then Template instance was already created.
#
# Otherwise, search for Template instance in parent classes, and make a copy of it.
if not isinstance(template_obj, Unset):
comp_media._template = template_obj
else:
parent_template = _get_comp_cls_attr(comp_cls, "_template")
# One of base classes has set `template` or `template_file` to `None`,
# or none of the base classes had set `template` or `template_file`
if parent_template is None:
comp_media._template = parent_template
# One of base classes has set `template` or `template_file` to string.
# Make a copy of the Template instance.
else:
comp_media._template = ensure_unique_template(comp_cls, parent_template)
def _normalize_media(media: Type[ComponentMediaInput]) -> None: def _normalize_media(media: Type[ComponentMediaInput]) -> None:
@ -973,11 +1001,10 @@ def _find_component_dir_containing_file(
def _get_asset( def _get_asset(
comp_cls: Type["Component"], comp_cls: Type["Component"],
comp_media: ComponentMedia, comp_media: ComponentMedia,
inlined_attr: str, inlined_attr: Literal["template", "js", "css"],
file_attr: str, file_attr: Literal["template_file", "js_file", "css_file"],
comp_dirs: List[Path], comp_dirs: List[Path],
type: Literal["template", "static"], ) -> Tuple[Union[str, Unset, None], Union[Template, Unset, None]]: # Tuple of (content, Template)
) -> Union[str, Unset, None]:
""" """
In case of Component's JS or CSS, one can either define that as "inlined" or as a file. In case of Component's JS or CSS, one can either define that as "inlined" or as a file.
@ -1010,7 +1037,7 @@ def _get_asset(
# pass # pass
# ``` # ```
if asset_content is UNSET and asset_file is UNSET: if asset_content is UNSET and asset_file is UNSET:
return UNSET return UNSET, UNSET
# Either file or content attr was set to `None` # Either file or content attr was set to `None`
# ```py # ```py
@ -1031,7 +1058,7 @@ def _get_asset(
if (asset_content in (UNSET, None) and asset_file is None) or ( if (asset_content in (UNSET, None) and asset_file is None) or (
asset_content is None and asset_file in (UNSET, None) asset_content is None and asset_file in (UNSET, None)
): ):
return None return None, None
# Received both inlined content and file name # Received both inlined content and file name
# ```py # ```py
@ -1061,24 +1088,36 @@ def _get_asset(
# At this point we can tell that only EITHER `asset_content` OR `asset_file` is set. # At this point we can tell that only EITHER `asset_content` OR `asset_file` is set.
# If the content was inlined into the component (e.g. `Component.template = "..."`) # If the content was inlined into the component (e.g. `Component.template = "..."`)
# then there's nothing to resolve. Return as is. # then there's nothing to resolve. Use it as is.
if asset_content is not UNSET: if not isinstance(asset_content, Unset):
return asset_content if asset_content is None:
return None, None
content: str = asset_content
# If we got inlined `Component.template`, then create a Template instance from it
# to trigger the extension hooks that may modify the template string.
if inlined_attr == "template":
# NOTE: `load_component_template()` applies `on_template_loaded()` and `on_template_compiled()` hooks.
template = load_component_template(comp_cls, filepath=None, content=content)
return template.source, template
# This else branch assumes that we were given a file name (possibly None)
# Load the contents of the file.
else:
if asset_file is None: if asset_file is None:
return None return None, None
# The rest of the code assumes that we were given only a file name
asset_file = cast(str, asset_file) asset_file = cast(str, asset_file)
if type == "template": if inlined_attr == "template":
# NOTE: While we return on the "source" (plain string) of the template, # NOTE: `load_component_template()` applies `on_template_loaded()` and `on_template_compiled()` hooks.
# by calling `load_component_template()`, we also cache the Template instance. template = load_component_template(comp_cls, filepath=asset_file, content=None)
# So later in Component's `render_impl()`, we don't have to re-compile the Template. return template.source, template
template = load_component_template(comp_cls, asset_file)
return template.source
# For static files, we have a few options: # Following code concerns with loading JS / CSS files.
# Here we have a few options:
#
# 1. Check if the file is in one of the components' directories # 1. Check if the file is in one of the components' directories
full_path = resolve_file(asset_file, comp_dirs) full_path = resolve_file(asset_file, comp_dirs)
@ -1091,12 +1130,26 @@ def _get_asset(
raise ValueError(f"Could not find {inlined_attr} file {asset_file}") raise ValueError(f"Could not find {inlined_attr} file {asset_file}")
# NOTE: Use explicit encoding for compat with Windows, see #1074 # NOTE: Use explicit encoding for compat with Windows, see #1074
asset_content = Path(full_path).read_text(encoding="utf8") content = Path(full_path).read_text(encoding="utf8")
# TODO: Apply `extensions.on_js_preprocess()` and `extensions.on_css_preprocess()` # NOTE: `on_template_loaded()` is already applied inside `load_component_template()`
# NOTE: `on_template_preprocess()` is already applied inside `load_component_template()` # but we still need to call extension hooks for JS / CSS content (whether inlined or not).
if inlined_attr == "js":
content = extensions.on_js_loaded(
OnJsLoadedContext(
component_cls=comp_cls,
content=content,
)
)
elif inlined_attr == "css":
content = extensions.on_css_loaded(
OnCssLoadedContext(
component_cls=comp_cls,
content=content,
)
)
return asset_content return content, None
def is_set(value: Union[T, Unset, None]) -> TypeGuard[T]: def is_set(value: Union[T, Unset, None]) -> TypeGuard[T]:

View file

@ -11,6 +11,7 @@ from django.template import Context
from django_components.util.misc import get_last_index from django_components.util.misc import get_last_index
_COMPONENT_CONTEXT_KEY = "_DJC_COMPONENT_CTX" _COMPONENT_CONTEXT_KEY = "_DJC_COMPONENT_CTX"
COMPONENT_IS_NESTED_KEY = "_DJC_COMPONENT_IS_NESTED"
_STRATEGY_CONTEXT_KEY = "DJC_DEPS_STRATEGY" _STRATEGY_CONTEXT_KEY = "DJC_DEPS_STRATEGY"
_INJECT_CONTEXT_KEY_PREFIX = "_DJC_INJECT__" _INJECT_CONTEXT_KEY_PREFIX = "_DJC_INJECT__"

View file

@ -107,13 +107,16 @@ def _cache_script(
cache.set(cache_key, script.strip()) cache.set(cache_key, script.strip())
def cache_component_js(comp_cls: Type["Component"]) -> None: def cache_component_js(comp_cls: Type["Component"], force: bool) -> None:
""" """
Cache the content from `Component.js`. This is the common JS that's shared Cache the content from `Component.js`. This is the common JS that's shared
among all instances of the same component. So even if the component is rendered multiple among all instances of the same component. So even if the component is rendered multiple
times, this JS is loaded only once. times, this JS is loaded only once.
""" """
if not comp_cls.js or not is_nonempty_str(comp_cls.js) or _is_script_in_cache(comp_cls, "js", None): if not comp_cls.js or not is_nonempty_str(comp_cls.js):
return None
if not force and _is_script_in_cache(comp_cls, "js", None):
return None return None
_cache_script( _cache_script(
@ -167,13 +170,16 @@ def wrap_component_js(comp_cls: Type["Component"], content: str) -> str:
return f"<script>{content}</script>" return f"<script>{content}</script>"
def cache_component_css(comp_cls: Type["Component"]) -> None: def cache_component_css(comp_cls: Type["Component"], force: bool) -> None:
""" """
Cache the content from `Component.css`. This is the common CSS that's shared Cache the content from `Component.css`. This is the common CSS that's shared
among all instances of the same component. So even if the component is rendered multiple among all instances of the same component. So even if the component is rendered multiple
times, this CSS is loaded only once. times, this CSS is loaded only once.
""" """
if not comp_cls.css or not is_nonempty_str(comp_cls.css) or _is_script_in_cache(comp_cls, "css", None): if not comp_cls.css or not is_nonempty_str(comp_cls.css):
return None
if not force and _is_script_in_cache(comp_cls, "css", None):
return None return None
_cache_script( _cache_script(

View file

@ -16,7 +16,7 @@ from typing import (
) )
import django.urls import django.urls
from django.template import Context from django.template import Context, Origin, Template
from django.urls import URLPattern, URLResolver, get_resolver, get_urlconf from django.urls import URLPattern, URLResolver, get_resolver, get_urlconf
from django_components.app_settings import app_settings from django_components.app_settings import app_settings
@ -168,6 +168,42 @@ class OnSlotRenderedContext(NamedTuple):
"""The rendered result of the slot""" """The rendered result of the slot"""
@mark_extension_hook_api
class OnTemplateLoadedContext(NamedTuple):
component_cls: Type["Component"]
"""The Component class whose template was loaded"""
content: str
"""The template string"""
origin: Optional[Origin]
"""The origin of the template"""
name: Optional[str]
"""The name of the template"""
@mark_extension_hook_api
class OnTemplateCompiledContext(NamedTuple):
component_cls: Type["Component"]
"""The Component class whose template was loaded"""
template: Template
"""The compiled template object"""
@mark_extension_hook_api
class OnCssLoadedContext(NamedTuple):
component_cls: Type["Component"]
"""The Component class whose CSS was loaded"""
content: str
"""The CSS content (string)"""
@mark_extension_hook_api
class OnJsLoadedContext(NamedTuple):
component_cls: Type["Component"]
"""The Component class whose JS was loaded"""
content: str
"""The JS content (string)"""
################################################ ################################################
# EXTENSIONS CORE # EXTENSIONS CORE
################################################ ################################################
@ -763,6 +799,108 @@ class ComponentExtension(metaclass=ExtensionMeta):
""" """
pass pass
##########################
# Template / JS / CSS hooks
##########################
def on_template_loaded(self, ctx: OnTemplateLoadedContext) -> Optional[str]:
"""
Called when a Component's template is loaded as a string.
This hook runs only once per [`Component`](../api#django_components.Component) class and works for both
[`Component.template`](../api#django_components.Component.template) and
[`Component.template_file`](../api#django_components.Component.template_file).
Use this hook to read or modify the template before it's compiled.
To modify the template, return a new string from this hook.
**Example:**
```python
from django_components import ComponentExtension, OnTemplateLoadedContext
class MyExtension(ComponentExtension):
def on_template_loaded(self, ctx: OnTemplateLoadedContext) -> Optional[str]:
# Modify the template
return ctx.content.replace("Hello", "Hi")
```
"""
pass
def on_template_compiled(self, ctx: OnTemplateCompiledContext) -> None:
"""
Called when a Component's template is compiled
into a [`Template`](https://docs.djangoproject.com/en/5.2/ref/templates/api/#django.template.Template) object.
This hook runs only once per [`Component`](../api#django_components.Component) class and works for both
[`Component.template`](../api#django_components.Component.template) and
[`Component.template_file`](../api#django_components.Component.template_file).
Use this hook to read or modify the template (in-place) after it's compiled.
**Example:**
```python
from django_components import ComponentExtension, OnTemplateCompiledContext
class MyExtension(ComponentExtension):
def on_template_compiled(self, ctx: OnTemplateCompiledContext) -> None:
print(f"Template origin: {ctx.template.origin.name}")
```
"""
pass
def on_css_loaded(self, ctx: OnCssLoadedContext) -> Optional[str]:
"""
Called when a Component's CSS is loaded as a string.
This hook runs only once per [`Component`](../api#django_components.Component) class and works for both
[`Component.css`](../api#django_components.Component.css) and
[`Component.css_file`](../api#django_components.Component.css_file).
Use this hook to read or modify the CSS.
To modify the CSS, return a new string from this hook.
**Example:**
```python
from django_components import ComponentExtension, OnCssLoadedContext
class MyExtension(ComponentExtension):
def on_css_loaded(self, ctx: OnCssLoadedContext) -> Optional[str]:
# Modify the CSS
return ctx.content.replace("Hello", "Hi")
```
"""
pass
def on_js_loaded(self, ctx: OnJsLoadedContext) -> Optional[str]:
"""
Called when a Component's JS is loaded as a string.
This hook runs only once per [`Component`](../api#django_components.Component) class and works for both
[`Component.js`](../api#django_components.Component.js) and
[`Component.js_file`](../api#django_components.Component.js_file).
Use this hook to read or modify the JS.
To modify the JS, return a new string from this hook.
**Example:**
```python
from django_components import ComponentExtension, OnCssLoadedContext
class MyExtension(ComponentExtension):
def on_js_loaded(self, ctx: OnJsLoadedContext) -> Optional[str]:
# Modify the JS
return ctx.content.replace("Hello", "Hi")
```
"""
pass
########################## ##########################
# Tags lifecycle hooks # Tags lifecycle hooks
########################## ##########################
@ -909,7 +1047,7 @@ class ExtensionManager:
# - `MyExtensionBase` is the base class that the extension class inherits from. # - `MyExtensionBase` is the base class that the extension class inherits from.
bases_list = [ext_base_class] bases_list = [ext_base_class]
all_extensions_defaults = app_settings._settings.extensions_defaults or {} all_extensions_defaults = app_settings.EXTENSIONS_DEFAULTS or {}
extension_defaults = all_extensions_defaults.get(extension.name, None) extension_defaults = all_extensions_defaults.get(extension.name, None)
if extension_defaults: if extension_defaults:
# Create dummy class that holds the extension defaults # Create dummy class that holds the extension defaults
@ -1169,9 +1307,34 @@ class ExtensionManager:
return ctx.result, ctx.error return ctx.result, ctx.error
########################## ##########################
# Tags lifecycle hooks # Template / JS / CSS hooks
########################## ##########################
def on_template_loaded(self, ctx: OnTemplateLoadedContext) -> str:
for extension in self.extensions:
content = extension.on_template_loaded(ctx)
if content is not None:
ctx = ctx._replace(content=content)
return ctx.content
def on_template_compiled(self, ctx: OnTemplateCompiledContext) -> None:
for extension in self.extensions:
extension.on_template_compiled(ctx)
def on_css_loaded(self, ctx: OnCssLoadedContext) -> str:
for extension in self.extensions:
content = extension.on_css_loaded(ctx)
if content is not None:
ctx = ctx._replace(content=content)
return ctx.content
def on_js_loaded(self, ctx: OnJsLoadedContext) -> str:
for extension in self.extensions:
content = extension.on_js_loaded(ctx)
if content is not None:
ctx = ctx._replace(content=content)
return ctx.content
def on_slot_rendered(self, ctx: OnSlotRenderedContext) -> Optional[str]: def on_slot_rendered(self, ctx: OnSlotRenderedContext) -> Optional[str]:
for extension in self.extensions: for extension in self.extensions:
result = extension.on_slot_rendered(ctx) result = extension.on_slot_rendered(ctx)

View file

@ -0,0 +1,21 @@
from django_components.dependencies import cache_component_css, cache_component_js
from django_components.extension import (
ComponentExtension,
OnComponentClassCreatedContext,
)
class DependenciesExtension(ComponentExtension):
"""
This extension adds a nested `Dependencies` class to each `Component`.
This extension is automatically added to all components.
"""
name = "dependencies"
# Cache the component's JS and CSS scripts when the class is created, so that
# components' JS/CSS files are accessible even before having to render the component first.
def on_component_class_created(self, ctx: OnComponentClassCreatedContext) -> None:
cache_component_js(ctx.component_cls, force=True)
cache_component_css(ctx.component_cls, force=True)

View file

@ -28,7 +28,7 @@ from django.utils.html import conditional_escape
from django.utils.safestring import SafeString, mark_safe from django.utils.safestring import SafeString, mark_safe
from django_components.app_settings import ContextBehavior from django_components.app_settings import ContextBehavior
from django_components.context import _COMPONENT_CONTEXT_KEY, _INJECT_CONTEXT_KEY_PREFIX from django_components.context import _COMPONENT_CONTEXT_KEY, _INJECT_CONTEXT_KEY_PREFIX, COMPONENT_IS_NESTED_KEY
from django_components.extension import OnSlotRenderedContext, extensions from django_components.extension import OnSlotRenderedContext, extensions
from django_components.node import BaseNode from django_components.node import BaseNode
from django_components.perfutil.component import component_context_cache from django_components.perfutil.component import component_context_cache
@ -742,7 +742,7 @@ class SlotNode(BaseNode):
# 1. Using the "django" context behavior # 1. Using the "django" context behavior
# 2. AND the slot fill is defined in the root template # 2. AND the slot fill is defined in the root template
# #
# Then `ctx_with_fills.fills` does NOT contain any fills (`{% fill %}`). So in this case, # Then `ctx_with_fills.component.raw_slots` does NOT contain any fills (`{% fill %}`). So in this case,
# we need to use a different strategy to find the fills Context layer that contains the fills. # we need to use a different strategy to find the fills Context layer that contains the fills.
# #
# ------------------------------------------------------------------------------------------ # ------------------------------------------------------------------------------------------
@ -825,7 +825,7 @@ class SlotNode(BaseNode):
if parent_index is not None: if parent_index is not None:
ctx_id_with_fills = context.dicts[parent_index][_COMPONENT_CONTEXT_KEY] ctx_id_with_fills = context.dicts[parent_index][_COMPONENT_CONTEXT_KEY]
ctx_with_fills = component_context_cache[ctx_id_with_fills] ctx_with_fills = component_context_cache[ctx_id_with_fills]
slot_fills = ctx_with_fills.fills slot_fills = ctx_with_fills.component.raw_slots
# Add trace message when slot_fills are overwritten # Add trace message when slot_fills are overwritten
trace_component_msg( trace_component_msg(
@ -1585,8 +1585,6 @@ def _nodelist_to_slot(
# and binds the context. # and binds the context.
template = Template("") template = Template("")
template.nodelist = nodelist template.nodelist = nodelist
# This allows the template to access current RenderContext layer.
template._djc_is_component_nested = True
def render_func(ctx: SlotContext) -> SlotResult: def render_func(ctx: SlotContext) -> SlotResult:
context = ctx.context or Context() context = ctx.context or Context()
@ -1639,10 +1637,12 @@ def _nodelist_to_slot(
trace_component_msg("RENDER_NODELIST", component_name, component_id=None, slot_name=slot_name) trace_component_msg("RENDER_NODELIST", component_name, component_id=None, slot_name=slot_name)
# We wrap the slot nodelist in Template. However, we also override Django's `Template.render()` # NOTE 1: We wrap the slot nodelist in Template. However, we also override Django's `Template.render()`
# to call `render_dependencies()` on the results. So we need to set the strategy to `ignore` # to call `render_dependencies()` on the results. So we need to set the strategy to `ignore`
# so that the dependencies are processed only once the whole component tree is rendered. # so that the dependencies are processed only once the whole component tree is rendered.
with context.push({"DJC_DEPS_STRATEGY": "ignore"}): # NOTE 2: We also set `_DJC_COMPONENT_IS_NESTED` to `True` so that the template can access
# current RenderContext layer.
with context.push({"DJC_DEPS_STRATEGY": "ignore", COMPONENT_IS_NESTED_KEY: True}):
rendered = template.render(context) rendered = template.render(context)
# After the rendering is done, remove the `extra_context` from the context stack # After the rendering is done, remove the `extra_context` from the context stack

View file

@ -1,6 +1,6 @@
import sys import sys
from contextlib import contextmanager from contextlib import contextmanager
from typing import TYPE_CHECKING, Any, Dict, Generator, List, Optional, Type, Union, cast from typing import TYPE_CHECKING, Any, Dict, Generator, List, Optional, Type, Union
from weakref import ReferenceType, ref from weakref import ReferenceType, ref
from django.core.exceptions import ImproperlyConfigured from django.core.exceptions import ImproperlyConfigured
@ -8,7 +8,7 @@ from django.template import Context, Origin, Template
from django.template.loader import get_template as django_get_template from django.template.loader import get_template as django_get_template
from django_components.cache import get_template_cache from django_components.cache import get_template_cache
from django_components.util.django_monkeypatch import is_template_cls_patched from django_components.util.django_monkeypatch import is_cls_patched
from django_components.util.loader import get_component_dirs from django_components.util.loader import get_component_dirs
from django_components.util.logger import trace_component_msg from django_components.util.logger import trace_component_msg
from django_components.util.misc import get_import_path, get_module_info from django_components.util.misc import get_import_path, get_module_info
@ -98,7 +98,7 @@ def prepare_component_template(
yield template yield template
return return
if not is_template_cls_patched(template): if not is_cls_patched(template):
raise RuntimeError( raise RuntimeError(
"Django-components received a Template instance which was not patched." "Django-components received a Template instance which was not patched."
"If you are using Django's Template class, check if you added django-components" "If you are using Django's Template class, check if you added django-components"
@ -170,30 +170,45 @@ def _maybe_bind_template(context: Context, template: Template) -> Generator[None
loading_components: List["ComponentRef"] = [] loading_components: List["ComponentRef"] = []
def load_component_template(component_cls: Type["Component"], filepath: str) -> Template: def load_component_template(
if component_cls._template is not None: component_cls: Type["Component"],
return component_cls._template filepath: Optional[str] = None,
content: Optional[str] = None,
) -> Template:
if filepath is None and content is None:
raise ValueError("Either `filepath` or `content` must be provided.")
loading_components.append(ref(component_cls)) loading_components.append(ref(component_cls))
# Use Django's `get_template()` to load the template if filepath is not None:
# Use Django's `get_template()` to load the template file
template = _load_django_template(filepath) template = _load_django_template(filepath)
template = ensure_unique_template(component_cls, template)
# If template.origin.component_cls is already set, then this elif content is not None:
# Template instance was cached by Django / template loaders. template = _create_template_from_string(component_cls, content, is_component_template=True)
else:
raise ValueError("Received both `filepath` and `content`. These are mutually exclusive.")
loading_components.pop()
return template
# When loading a Template instance, it may be cached by Django / template loaders.
# In that case we want to make a copy of the template which would # In that case we want to make a copy of the template which would
# be owned by the current Component class. # be owned by the current Component class.
# Thus each Component has it's own Template instance with their own Origins # Thus each Component has it's own Template instance with their own Origins
# pointing to the correct Component class. # pointing to the correct Component class.
if get_component_from_origin(template.origin) is not None: def ensure_unique_template(component_cls: Type["Component"], template: Template) -> Template:
# Use `template.origin.component_cls` to check if the template was cached by Django / template loaders.
if get_component_from_origin(template.origin) is None:
set_component_to_origin(template.origin, component_cls)
else:
origin_copy = Origin(template.origin.name, template.origin.template_name, template.origin.loader) origin_copy = Origin(template.origin.name, template.origin.template_name, template.origin.loader)
set_component_to_origin(origin_copy, component_cls) set_component_to_origin(origin_copy, component_cls)
template = Template(template.source, origin=origin_copy, name=template.name, engine=template.engine) template = Template(template.source, origin=origin_copy, name=template.name, engine=template.engine)
component_cls._template = template
loading_components.pop()
return template return template
@ -250,23 +265,12 @@ def _get_component_template(component: "Component") -> Optional[Template]:
template = None template = None
template_string = template_sources["get_template"] template_string = template_sources["get_template"]
elif component.template or component.template_file: elif component.template or component.template_file:
# If the template was loaded from `Component.template_file`, then the Template # If the template was loaded from `Component.template` or `Component.template_file`,
# instance was already created and cached in `Component._template`. # then the Template instance was already created and cached in `Component._template`.
# #
# NOTE: This is important to keep in mind, because the implication is that we should # NOTE: This is important to keep in mind, because the implication is that we should
# treat Templates AND their nodelists as IMMUTABLE. # treat Templates AND their nodelists as IMMUTABLE.
if component.__class__._template is not None: template = component.__class__._component_media._template # type: ignore[attr-defined]
template = component.__class__._template
template_string = None
# Otherwise user have set `Component.template` as string and we still need to
# create the instance.
else:
template = _create_template_from_string(
component,
# NOTE: We can't reach this branch if `Component.template` is None
cast(str, component.template),
is_component_template=True,
)
template_string = None template_string = None
# No template # No template
else: else:
@ -278,7 +282,7 @@ def _get_component_template(component: "Component") -> Optional[Template]:
return template return template
# Create the template from the string # Create the template from the string
elif template_string is not None: elif template_string is not None:
return _create_template_from_string(component, template_string) return _create_template_from_string(component.__class__, template_string)
# Otherwise, Component has no template - this is valid, as it may be instead rendered # Otherwise, Component has no template - this is valid, as it may be instead rendered
# via `Component.on_render()` # via `Component.on_render()`
@ -286,7 +290,7 @@ def _get_component_template(component: "Component") -> Optional[Template]:
def _create_template_from_string( def _create_template_from_string(
component: "Component", component: Type["Component"],
template_string: str, template_string: str,
is_component_template: bool = False, is_component_template: bool = False,
) -> Template: ) -> Template:
@ -307,18 +311,17 @@ def _create_template_from_string(
# ``` # ```
# #
# See https://docs.djangoproject.com/en/5.2/howto/custom-template-backend/#template-origin-api # See https://docs.djangoproject.com/en/5.2/howto/custom-template-backend/#template-origin-api
_, _, module_filepath = get_module_info(component.__class__) _, _, module_filepath = get_module_info(component)
origin = Origin( origin = Origin(
name=f"{module_filepath}::{component.__class__.__name__}", name=f"{module_filepath}::{component.__name__}",
template_name=None, template_name=None,
loader=None, loader=None,
) )
set_component_to_origin(origin, component.__class__) set_component_to_origin(origin, component)
if is_component_template: if is_component_template:
template = Template(template_string, name=origin.template_name, origin=origin) template = Template(template_string, name=origin.template_name, origin=origin)
component.__class__._template = template
else: else:
# TODO_V1 - `cached_template()` won't be needed as there will be only 1 template per component # TODO_V1 - `cached_template()` won't be needed as there will be only 1 template per component
# so we will be able to instead use `template_cache` to store the template # so we will be able to instead use `template_cache` to store the template

View file

@ -1,23 +1,28 @@
from typing import Any, Optional, Type from typing import Any, Optional, Type
from django.template import Context, NodeList, Template from django.template import Context, NodeList, Template
from django.template.base import Origin, Parser from django.template.base import Node, Origin, Parser
from django.template.loader_tags import IncludeNode
from django_components.context import _COMPONENT_CONTEXT_KEY, _STRATEGY_CONTEXT_KEY from django_components.context import _COMPONENT_CONTEXT_KEY, _STRATEGY_CONTEXT_KEY, COMPONENT_IS_NESTED_KEY
from django_components.dependencies import COMPONENT_COMMENT_REGEX, render_dependencies from django_components.dependencies import COMPONENT_COMMENT_REGEX, render_dependencies
from django_components.extension import OnTemplateCompiledContext, OnTemplateLoadedContext, extensions
from django_components.util.template_parser import parse_template from django_components.util.template_parser import parse_template
# In some cases we can't work around Django's design, and need to patch the template class. # In some cases we can't work around Django's design, and need to patch the template class.
def monkeypatch_template_cls(template_cls: Type[Template]) -> None: def monkeypatch_template_cls(template_cls: Type[Template]) -> None:
if is_cls_patched(template_cls):
return
monkeypatch_template_init(template_cls) monkeypatch_template_init(template_cls)
monkeypatch_template_compile_nodelist(template_cls) monkeypatch_template_compile_nodelist(template_cls)
monkeypatch_template_render(template_cls) monkeypatch_template_render(template_cls)
template_cls._djc_patched = True template_cls._djc_patched = True
# Patch `Template.__init__` to apply `extensions.on_template_preprocess()` if the template # Patch `Template.__init__` to apply `on_template_loaded()` and `on_template_compiled()`
# belongs to a Component. # extension hooks if the template belongs to a Component.
def monkeypatch_template_init(template_cls: Type[Template]) -> None: def monkeypatch_template_init(template_cls: Type[Template]) -> None:
original_init = template_cls.__init__ original_init = template_cls.__init__
@ -27,6 +32,7 @@ def monkeypatch_template_init(template_cls: Type[Template]) -> None:
self: Template, self: Template,
template_string: Any, template_string: Any,
origin: Optional[Origin] = None, origin: Optional[Origin] = None,
name: Optional[str] = None,
*args: Any, *args: Any,
**kwargs: Any, **kwargs: Any,
) -> None: ) -> None:
@ -56,11 +62,27 @@ def monkeypatch_template_init(template_cls: Type[Template]) -> None:
component_cls = None component_cls = None
if component_cls is not None: if component_cls is not None:
# TODO - Apply extensions.on_template_preprocess() here. template_string = str(template_string)
# Then also test both cases when template as `template` or `template_file`. template_string = extensions.on_template_loaded(
pass OnTemplateLoadedContext(
component_cls=component_cls,
content=template_string,
origin=origin,
name=name,
)
)
original_init(self, template_string, origin, *args, **kwargs) # type: ignore[misc] # Calling original `Template.__init__` should also compile the template into a Nodelist
# via `Template.compile_nodelist()`.
original_init(self, template_string, origin, name, *args, **kwargs) # type: ignore[misc]
if component_cls is not None:
extensions.on_template_compiled(
OnTemplateCompiledContext(
component_cls=component_cls,
template=self,
)
)
template_cls.__init__ = __init__ template_cls.__init__ = __init__
@ -110,7 +132,7 @@ def monkeypatch_template_compile_nodelist(template_cls: Type[Template]) -> None:
def monkeypatch_template_render(template_cls: Type[Template]) -> None: def monkeypatch_template_render(template_cls: Type[Template]) -> None:
# Modify `Template.render` to set `isolated_context` kwarg of `push_state` # Modify `Template.render` to set `isolated_context` kwarg of `push_state`
# based on our custom `Template._djc_is_component_nested`. # based on our custom `_DJC_COMPONENT_IS_NESTED`.
# #
# Part of fix for https://github.com/django-components/django-components/issues/508 # Part of fix for https://github.com/django-components/django-components/issues/508
# #
@ -125,11 +147,11 @@ def monkeypatch_template_render(template_cls: Type[Template]) -> None:
# doesn't require the source to be parsed multiple times. User can pass extra args/kwargs, # doesn't require the source to be parsed multiple times. User can pass extra args/kwargs,
# and can modify the rendering behavior by overriding the `_render` method. # and can modify the rendering behavior by overriding the `_render` method.
# #
# NOTE 2: Instead of setting `Template._djc_is_component_nested`, alternatively we could # NOTE 2: Instead of setting `_DJC_COMPONENT_IS_NESTED` context key, alternatively we could
# have passed the value to `monkeypatch_template_render` directly. However, we intentionally # have passed the value to `monkeypatch_template_render` directly. However, we intentionally
# did NOT do that, so the monkey-patched method is more robust, and can be e.g. copied # did NOT do that, so the monkey-patched method is more robust, and can be e.g. copied
# to other. # to other.
if is_template_cls_patched(template_cls): if is_cls_patched(template_cls):
# Do not patch if done so already. This helps us avoid RecursionError # Do not patch if done so already. This helps us avoid RecursionError
return return
@ -137,12 +159,12 @@ def monkeypatch_template_render(template_cls: Type[Template]) -> None:
def _template_render(self: Template, context: Context, *args: Any, **kwargs: Any) -> str: def _template_render(self: Template, context: Context, *args: Any, **kwargs: Any) -> str:
"Display stage -- can be called many times" "Display stage -- can be called many times"
# We parametrized `isolated_context`, which was `True` in the original method. # We parametrized `isolated_context`, which was `True` in the original method.
if not hasattr(self, "_djc_is_component_nested"): if COMPONENT_IS_NESTED_KEY not in context:
isolated_context = True isolated_context = True
else: else:
# MUST be `True` for templates that are NOT import with `{% extends %}` tag, # MUST be `True` for templates that are NOT import with `{% extends %}` tag,
# and `False` otherwise. # and `False` otherwise.
isolated_context = not self._djc_is_component_nested isolated_context = not context[COMPONENT_IS_NESTED_KEY]
# This is original implementation, except we override `isolated_context`, # This is original implementation, except we override `isolated_context`,
# and we post-process the result with `render_dependencies()`. # and we post-process the result with `render_dependencies()`.
@ -189,5 +211,37 @@ def monkeypatch_template_render(template_cls: Type[Template]) -> None:
template_cls.render = _template_render template_cls.render = _template_render
def is_template_cls_patched(template_cls: Type[Template]) -> bool: def monkeypatch_include_node(include_node_cls: Type[Node]) -> None:
return getattr(template_cls, "_djc_patched", False) if is_cls_patched(include_node_cls):
return
monkeypatch_include_render(include_node_cls)
include_node_cls._djc_patched = True
def monkeypatch_include_render(include_node_cls: Type[Node]) -> None:
# Modify `IncludeNode.render()` (what renders `{% include %}` tag) so that the included
# template does NOT render the JS/CSS by itself.
#
# Instead, we want the parent template
# (which contains the `{% component %}` tag) to decide whether to render the JS/CSS.
#
# We achieve this by setting `DJC_DEPS_STRATEGY` to `ignore` in the context.
#
# Fix for https://github.com/django-components/django-components/issues/1296
if is_cls_patched(include_node_cls):
# Do not patch if done so already. This helps us avoid RecursionError
return
orig_include_render = include_node_cls.render
# NOTE: This implementation is based on Django v5.1.3)
def _include_render(self: IncludeNode, context: Context, *args: Any, **kwargs: Any) -> str:
with context.update({_STRATEGY_CONTEXT_KEY: "ignore"}):
return orig_include_render(self, context, *args, **kwargs)
include_node_cls.render = _include_render
def is_cls_patched(cls: Type[Any]) -> bool:
return getattr(cls, "_djc_patched", False)

View file

@ -25,7 +25,7 @@ def component_error_message(component_path: List[str]) -> Generator[None, None,
if not components: if not components:
orig_msg = str(err.args[0]) orig_msg = str(err.args[0])
else: else:
orig_msg = err.args[0].split("\n", 1)[-1] orig_msg = str(err.args[0]).split("\n", 1)[-1]
else: else:
orig_msg = str(err) orig_msg = str(err)

View file

@ -1,4 +1,4 @@
{% include "test_cached_component_inside_include_sub.html" %} {% include "component_inside_include_sub.html" %}
{% block content %} {% block content %}
THIS_IS_IN_BASE_TEMPLATE_SO_SHOULD_BE_OVERRIDDEN THIS_IS_IN_BASE_TEMPLATE_SO_SHOULD_BE_OVERRIDDEN
{% endblock %} {% endblock %}

View file

@ -50,8 +50,6 @@ class TestImportLibraries:
} }
) )
def test_import_libraries(self): def test_import_libraries(self):
# Ensure we start with a clean state
registry.clear()
all_components = registry.all().copy() all_components = registry.all().copy()
assert "single_file_component" not in all_components assert "single_file_component" not in all_components
assert "multi_file_component" not in all_components assert "multi_file_component" not in all_components
@ -80,8 +78,6 @@ class TestImportLibraries:
} }
) )
def test_import_libraries_map_modules(self): def test_import_libraries_map_modules(self):
# Ensure we start with a clean state
registry.clear()
all_components = registry.all().copy() all_components = registry.all().copy()
assert "single_file_component" not in all_components assert "single_file_component" not in all_components
assert "multi_file_component" not in all_components assert "multi_file_component" not in all_components

View file

@ -1,5 +1,3 @@
from django.core.cache.backends.locmem import LocMemCache
from django_components import Component, register from django_components import Component, register
from django_components.testing import djc_test from django_components.testing import djc_test
from django_components.util.cache import LRUCache from django_components.util.cache import LRUCache
@ -68,16 +66,26 @@ class TestCache:
@djc_test @djc_test
class TestComponentMediaCache: class TestComponentMediaCache:
@djc_test(components_settings={"cache": "test-cache"}) @djc_test(
def test_component_media_caching(self): components_settings={"cache": "test-cache"},
test_cache = LocMemCache( django_settings={
"test-cache", "CACHES": {
{ # See https://docs.djangoproject.com/en/5.2/topics/cache/#local-memory-caching
"test-cache": {
"BACKEND": "django.core.cache.backends.locmem.LocMemCache",
"LOCATION": "test-cache",
"TIMEOUT": None, # No timeout "TIMEOUT": None, # No timeout
"MAX_ENTRIES": None, # No max size "OPTIONS": {
"CULL_FREQUENCY": 3, "MAX_ENTRIES": 10_000,
},
},
},
}, },
) )
def test_component_media_caching(self):
from django.core.cache import caches
test_cache = caches["test-cache"]
@register("test_simple") @register("test_simple")
class TestSimpleComponent(Component): class TestSimpleComponent(Component):
@ -108,14 +116,6 @@ class TestComponentMediaCache:
def get_css_data(self, args, kwargs, slots, context): def get_css_data(self, args, kwargs, slots, context):
return {"color": "blue"} return {"color": "blue"}
# Register our test cache
from django.core.cache import caches
caches["test-cache"] = test_cache
# Render the components to trigger caching
TestMediaAndVarsComponent.render()
# Check that JS/CSS is cached for components that have them # Check that JS/CSS is cached for components that have them
assert test_cache.has_key(f"__components:{TestMediaAndVarsComponent.class_id}:js") assert test_cache.has_key(f"__components:{TestMediaAndVarsComponent.class_id}:js")
assert test_cache.has_key(f"__components:{TestMediaAndVarsComponent.class_id}:css") assert test_cache.has_key(f"__components:{TestMediaAndVarsComponent.class_id}:css")
@ -128,6 +128,9 @@ class TestComponentMediaCache:
assert test_cache.get(f"__components:{TestMediaNoVarsComponent.class_id}:js").strip() == "console.log('Hello from JS');" # noqa: E501 assert test_cache.get(f"__components:{TestMediaNoVarsComponent.class_id}:js").strip() == "console.log('Hello from JS');" # noqa: E501
assert test_cache.get(f"__components:{TestMediaNoVarsComponent.class_id}:css").strip() == ".novars-component { color: blue; }" # noqa: E501 assert test_cache.get(f"__components:{TestMediaNoVarsComponent.class_id}:css").strip() == ".novars-component { color: blue; }" # noqa: E501
# Render the components to trigger caching of JS/CSS variables from `get_js_data` / `get_css_data`
TestMediaAndVarsComponent.render()
# Check that we cache JS / CSS scripts generated from `get_js_data` / `get_css_data` # Check that we cache JS / CSS scripts generated from `get_js_data` / `get_css_data`
# NOTE: The hashes is generated from the data. # NOTE: The hashes is generated from the data.
js_vars_hash = "216ecc" js_vars_hash = "216ecc"

View file

@ -103,6 +103,7 @@ class TestExtensionsListCommand:
"===============\n" "===============\n"
"cache \n" "cache \n"
"defaults \n" "defaults \n"
"dependencies \n"
"view \n" "view \n"
"debug_highlight" "debug_highlight"
) )
@ -121,6 +122,7 @@ class TestExtensionsListCommand:
"===============\n" "===============\n"
"cache \n" "cache \n"
"defaults \n" "defaults \n"
"dependencies \n"
"view \n" "view \n"
"debug_highlight\n" "debug_highlight\n"
"empty \n" "empty \n"
@ -141,6 +143,7 @@ class TestExtensionsListCommand:
"===============\n" "===============\n"
"cache \n" "cache \n"
"defaults \n" "defaults \n"
"dependencies \n"
"view \n" "view \n"
"debug_highlight\n" "debug_highlight\n"
"empty \n" "empty \n"
@ -161,6 +164,7 @@ class TestExtensionsListCommand:
"===============\n" "===============\n"
"cache \n" "cache \n"
"defaults \n" "defaults \n"
"dependencies \n"
"view \n" "view \n"
"debug_highlight\n" "debug_highlight\n"
"empty \n" "empty \n"
@ -179,6 +183,7 @@ class TestExtensionsListCommand:
assert output.strip() == ( assert output.strip() == (
"cache \n" "cache \n"
"defaults \n" "defaults \n"
"dependencies \n"
"view \n" "view \n"
"debug_highlight\n" "debug_highlight\n"
"empty \n" "empty \n"
@ -206,7 +211,7 @@ class TestExtensionsRunCommand:
output output
== dedent( == dedent(
f""" f"""
usage: components ext run [-h] {{cache,defaults,view,debug_highlight,empty,dummy}} ... usage: components ext run [-h] {{dummy}} ...
Run a command added by an extension. Run a command added by an extension.
@ -214,12 +219,7 @@ class TestExtensionsRunCommand:
-h, --help show this help message and exit -h, --help show this help message and exit
subcommands: subcommands:
{{cache,defaults,view,debug_highlight,empty,dummy}} {{dummy}}
cache Run commands added by the 'cache' extension.
defaults Run commands added by the 'defaults' extension.
view Run commands added by the 'view' extension.
debug_highlight Run commands added by the 'debug_highlight' extension.
empty Run commands added by the 'empty' extension.
dummy Run commands added by the 'dummy' extension. dummy Run commands added by the 'dummy' extension.
""" """
).lstrip() ).lstrip()
@ -231,19 +231,23 @@ class TestExtensionsRunCommand:
def test_run_command_ext_empty(self): def test_run_command_ext_empty(self):
out = StringIO() out = StringIO()
with patch("sys.stdout", new=out): with patch("sys.stdout", new=out):
call_command("components", "ext", "run", "empty") call_command("components", "ext", "run", "dummy")
output = out.getvalue() output = out.getvalue()
assert ( assert (
output output
== dedent( == dedent(
f""" f"""
usage: components ext run empty [-h] usage: components ext run dummy [-h] {{dummy_cmd}} ...
Run commands added by the 'empty' extension. Run commands added by the 'dummy' extension.
{OPTIONS_TITLE}: {OPTIONS_TITLE}:
-h, --help show this help message and exit -h, --help show this help message and exit
subcommands:
{{dummy_cmd}}
dummy_cmd Dummy command description.
""" """
).lstrip() ).lstrip()
) )

View file

@ -219,7 +219,7 @@ class TestComponentCache:
template = Template( template = Template(
""" """
{% extends "test_cached_component_inside_include_base.html" %} {% extends "component_inside_include_base.html" %}
{% block content %} {% block content %}
THIS_IS_IN_ACTUAL_TEMPLATE_SO_SHOULD_NOT_BE_OVERRIDDEN THIS_IS_IN_ACTUAL_TEMPLATE_SO_SHOULD_NOT_BE_OVERRIDDEN
{% endblock %} {% endblock %}

View file

@ -15,7 +15,6 @@ from django.utils.safestring import mark_safe
from pytest_django.asserts import assertHTMLEqual, assertInHTML from pytest_django.asserts import assertHTMLEqual, assertInHTML
from django_components import Component, autodiscover, registry, render_dependencies, types from django_components import Component, autodiscover, registry, render_dependencies, types
from django_components.component_media import UNSET
from django_components.testing import djc_test from django_components.testing import djc_test
from .testutils import setup_test_config from .testutils import setup_test_config
@ -46,7 +45,7 @@ class TestMainMedia:
rendered = TestComponent.render() rendered = TestComponent.render()
assertInHTML( assertInHTML(
'<div class="html-css-only" data-djc-id-ca1bc3e>Content</div>', '<div class="html-css-only" data-djc-id-ca1bc40>Content</div>',
rendered, rendered,
) )
assertInHTML( assertInHTML(
@ -70,6 +69,9 @@ class TestMainMedia:
assert TestComponent.css == ".html-css-only { color: blue; }" assert TestComponent.css == ".html-css-only { color: blue; }"
assert TestComponent.js == "console.log('HTML and JS only');" assert TestComponent.js == "console.log('HTML and JS only');"
assert isinstance(TestComponent._template, Template)
assert TestComponent._template.origin.component_cls is TestComponent
@djc_test( @djc_test(
django_settings={ django_settings={
"STATICFILES_DIRS": [ "STATICFILES_DIRS": [
@ -127,6 +129,9 @@ class TestMainMedia:
assert TestComponent.css == ".html-css-only {\n color: blue;\n}\n" assert TestComponent.css == ".html-css-only {\n color: blue;\n}\n"
assert TestComponent.js == 'console.log("JS file");\n' assert TestComponent.js == 'console.log("JS file");\n'
assert isinstance(TestComponent._template, Template)
assert TestComponent._template.origin.component_cls is TestComponent
@djc_test( @djc_test(
django_settings={ django_settings={
"STATICFILES_DIRS": [ "STATICFILES_DIRS": [
@ -151,6 +156,9 @@ class TestMainMedia:
assert ".html-css-only {\n color: blue;\n}" in TestComponent.css # type: ignore[operator] assert ".html-css-only {\n color: blue;\n}" in TestComponent.css # type: ignore[operator]
assert 'console.log("HTML and JS only");' in TestComponent.js # type: ignore[operator] assert 'console.log("HTML and JS only");' in TestComponent.js # type: ignore[operator]
assert isinstance(TestComponent._template, Template)
assert TestComponent._template.origin.component_cls is TestComponent
rendered = Template( rendered = Template(
""" """
{% load component_tags %} {% load component_tags %}
@ -195,13 +203,18 @@ class TestMainMedia:
class TestComponent(AppLvlCompComponent): class TestComponent(AppLvlCompComponent):
pass pass
# NOTE: Since this is a subclass, actual CSS is defined on the parent class, and thus # NOTE: Currently the components' JS/CSS are loaded eagerly, to make the JS/CSS
# the corresponding ComponentMedia instance is also on the parent class. # files available via endpoints. If that is no longer true, uncomment the
assert AppLvlCompComponent._component_media.css is UNSET # type: ignore[attr-defined] # following lines to test the lazy loading of the CSS.
assert AppLvlCompComponent._component_media.css_file == "app_lvl_comp.css" # type: ignore[attr-defined] #
# # Since this is a subclass, actual CSS is defined on the parent class, and thus
# Access the property to load the CSS # # the corresponding ComponentMedia instance is also on the parent class.
_ = TestComponent.css # assert AppLvlCompComponent._component_media.css is UNSET # type: ignore[attr-defined]
# assert AppLvlCompComponent._component_media.css_file == "app_lvl_comp.css" # type: ignore[attr-defined]
# assert AppLvlCompComponent._component_media._template is UNSET # type: ignore[attr-defined]
#
# # Access the property to load the CSS
# _ = TestComponent.css
assert AppLvlCompComponent._component_media.css == (".html-css-only {\n" " color: blue;\n" "}\n") # type: ignore[attr-defined] assert AppLvlCompComponent._component_media.css == (".html-css-only {\n" " color: blue;\n" "}\n") # type: ignore[attr-defined]
assert AppLvlCompComponent._component_media.css_file == "app_lvl_comp/app_lvl_comp.css" # type: ignore[attr-defined] assert AppLvlCompComponent._component_media.css_file == "app_lvl_comp/app_lvl_comp.css" # type: ignore[attr-defined]
@ -218,6 +231,9 @@ class TestMainMedia:
assert AppLvlCompComponent._component_media.js == 'console.log("JS file");\n' # type: ignore[attr-defined] assert AppLvlCompComponent._component_media.js == 'console.log("JS file");\n' # type: ignore[attr-defined]
assert AppLvlCompComponent._component_media.js_file == "app_lvl_comp/app_lvl_comp.js" # type: ignore[attr-defined] assert AppLvlCompComponent._component_media.js_file == "app_lvl_comp/app_lvl_comp.js" # type: ignore[attr-defined]
assert isinstance(AppLvlCompComponent._component_media._template, Template) # type: ignore[attr-defined]
assert AppLvlCompComponent._component_media._template.origin.component_cls is AppLvlCompComponent # type: ignore[attr-defined]
def test_html_variable_filtered(self): def test_html_variable_filtered(self):
class FilteredComponent(Component): class FilteredComponent(Component):
template: types.django_html = """ template: types.django_html = """
@ -1037,73 +1053,121 @@ class TestSubclassingAttributes:
class TestComp(Component): class TestComp(Component):
js = None js = None
js_file = None js_file = None
template = None
template_file = None
assert TestComp.js is None assert TestComp.js is None
assert TestComp.js_file is None assert TestComp.js_file is None
assert TestComp.template is None
assert TestComp.template_file is None
def test_mixing_none_and_non_none_raises(self): def test_mixing_none_and_non_none_raises(self):
with pytest.raises( with pytest.raises(
ImproperlyConfigured, ImproperlyConfigured,
match=re.escape("Received non-empty value from both 'js' and 'js_file' in Component TestComp"), match=re.escape("Received non-empty value from both 'template' and 'template_file' in Component TestComp"),
): ):
class TestComp(Component): class TestComp(Component):
js = "console.log('hi')" js = "console.log('hi')"
js_file = None js_file = None
template = "<h1>hi</h1>"
template_file = None
def test_both_non_none_raises(self): def test_both_non_none_raises(self):
with pytest.raises( with pytest.raises(
ImproperlyConfigured, ImproperlyConfigured,
match=re.escape("Received non-empty value from both 'js' and 'js_file' in Component TestComp"), match=re.escape("Received non-empty value from both 'template' and 'template_file' in Component TestComp"),
): ):
class TestComp(Component): class TestComp(Component):
js = "console.log('hi')" js = "console.log('hi')"
js_file = "file.js" js_file = "file.js"
template = "<h1>hi</h1>"
template_file = "file.html"
def test_parent_non_null_child_non_null(self): def test_parent_non_null_child_non_null(self):
class ParentComp(Component): class ParentComp(Component):
js = "console.log('parent')" js = "console.log('parent')"
template = "<h1>parent</h1>"
class TestComp(ParentComp): class TestComp(ParentComp):
js = "console.log('child')" js = "console.log('child')"
template = "<h1>child</h1>"
assert TestComp.js == "console.log('child')" assert TestComp.js == "console.log('child')"
assert TestComp.js_file is None assert TestComp.js_file is None
assert TestComp.template == "<h1>child</h1>"
assert TestComp.template_file is None
assert isinstance(ParentComp._template, Template)
assert ParentComp._template.source == "<h1>parent</h1>"
assert ParentComp._template.origin.component_cls == ParentComp
assert isinstance(TestComp._template, Template)
assert TestComp._template.source == "<h1>child</h1>"
assert TestComp._template.origin.component_cls == TestComp
def test_parent_null_child_non_null(self): def test_parent_null_child_non_null(self):
class ParentComp(Component): class ParentComp(Component):
js = None js = None
template = None
class TestComp(ParentComp): class TestComp(ParentComp):
js = "console.log('child')" js = "console.log('child')"
template = "<h1>child</h1>"
assert TestComp.js == "console.log('child')" assert TestComp.js == "console.log('child')"
assert TestComp.js_file is None assert TestComp.js_file is None
assert TestComp.template == "<h1>child</h1>"
assert TestComp.template_file is None
assert ParentComp._template is None
assert isinstance(TestComp._template, Template)
assert TestComp._template.source == "<h1>child</h1>"
assert TestComp._template.origin.component_cls == TestComp
def test_parent_non_null_child_null(self): def test_parent_non_null_child_null(self):
class ParentComp(Component): class ParentComp(Component):
js: Optional[str] = "console.log('parent')" js: Optional[str] = "console.log('parent')"
template: Optional[str] = "<h1>parent</h1>"
class TestComp(ParentComp): class TestComp(ParentComp):
js = None js = None
template = None
assert TestComp.js is None assert TestComp.js is None
assert TestComp.js_file is None assert TestComp.js_file is None
assert TestComp.template is None
assert TestComp.template_file is None
assert TestComp._template is None
assert isinstance(ParentComp._template, Template)
assert ParentComp._template.source == "<h1>parent</h1>"
assert ParentComp._template.origin.component_cls == ParentComp
def test_parent_null_child_null(self): def test_parent_null_child_null(self):
class ParentComp(Component): class ParentComp(Component):
js = None js = None
template = None
class TestComp(ParentComp): class TestComp(ParentComp):
js = None js = None
template = None
assert TestComp.js is None assert TestComp.js is None
assert TestComp.js_file is None assert TestComp.js_file is None
assert TestComp.template is None
assert TestComp.template_file is None
assert TestComp._template is None
assert ParentComp._template is None
def test_grandparent_non_null_parent_pass_child_pass(self): def test_grandparent_non_null_parent_pass_child_pass(self):
class GrandParentComp(Component): class GrandParentComp(Component):
js = "console.log('grandparent')" js = "console.log('grandparent')"
template = "<h1>grandparent</h1>"
class ParentComp(GrandParentComp): class ParentComp(GrandParentComp):
pass pass
@ -1113,45 +1177,97 @@ class TestSubclassingAttributes:
assert TestComp.js == "console.log('grandparent')" assert TestComp.js == "console.log('grandparent')"
assert TestComp.js_file is None assert TestComp.js_file is None
assert TestComp.template == "<h1>grandparent</h1>"
assert TestComp.template_file is None
assert isinstance(GrandParentComp._template, Template)
assert GrandParentComp._template.source == "<h1>grandparent</h1>"
assert GrandParentComp._template.origin.component_cls == GrandParentComp
assert isinstance(ParentComp._template, Template)
assert ParentComp._template.source == "<h1>grandparent</h1>"
assert ParentComp._template.origin.component_cls == ParentComp
assert isinstance(TestComp._template, Template)
assert TestComp._template.source == "<h1>grandparent</h1>"
assert TestComp._template.origin.component_cls == TestComp
def test_grandparent_non_null_parent_null_child_pass(self): def test_grandparent_non_null_parent_null_child_pass(self):
class GrandParentComp(Component): class GrandParentComp(Component):
js: Optional[str] = "console.log('grandparent')" js: Optional[str] = "console.log('grandparent')"
template: Optional[str] = "<h1>grandparent</h1>"
class ParentComp(GrandParentComp): class ParentComp(GrandParentComp):
js = None js = None
template = None
class TestComp(ParentComp): class TestComp(ParentComp):
pass pass
assert TestComp.js is None assert TestComp.js is None
assert TestComp.js_file is None assert TestComp.js_file is None
assert TestComp.template is None
assert TestComp.template_file is None
assert isinstance(GrandParentComp._template, Template)
assert GrandParentComp._template.source == "<h1>grandparent</h1>"
assert GrandParentComp._template.origin.component_cls == GrandParentComp
assert ParentComp._template is None
assert TestComp._template is None
def test_grandparent_non_null_parent_pass_child_non_null(self): def test_grandparent_non_null_parent_pass_child_non_null(self):
class GrandParentComp(Component): class GrandParentComp(Component):
js = "console.log('grandparent')" js = "console.log('grandparent')"
template = "<h1>grandparent</h1>"
class ParentComp(GrandParentComp): class ParentComp(GrandParentComp):
pass pass
class TestComp(ParentComp): class TestComp(ParentComp):
js = "console.log('child')" js = "console.log('child')"
template = "<h1>child</h1>"
assert TestComp.js == "console.log('child')" assert TestComp.js == "console.log('child')"
assert TestComp.js_file is None assert TestComp.js_file is None
assert TestComp.template == "<h1>child</h1>"
assert TestComp.template_file is None
assert isinstance(GrandParentComp._template, Template)
assert GrandParentComp._template.source == "<h1>grandparent</h1>"
assert GrandParentComp._template.origin.component_cls == GrandParentComp
assert isinstance(ParentComp._template, Template)
assert ParentComp._template.source == "<h1>grandparent</h1>"
assert ParentComp._template.origin.component_cls == ParentComp
assert isinstance(TestComp._template, Template)
assert TestComp._template.source == "<h1>child</h1>"
assert TestComp._template.origin.component_cls == TestComp
def test_grandparent_null_parent_pass_child_non_null(self): def test_grandparent_null_parent_pass_child_non_null(self):
class GrandParentComp(Component): class GrandParentComp(Component):
js = None js = None
template = None
class ParentComp(GrandParentComp): class ParentComp(GrandParentComp):
pass pass
class TestComp(ParentComp): class TestComp(ParentComp):
js = "console.log('child')" js = "console.log('child')"
template = "<h1>child</h1>"
assert TestComp.js == "console.log('child')" assert TestComp.js == "console.log('child')"
assert TestComp.js_file is None assert TestComp.js_file is None
assert TestComp.template == "<h1>child</h1>"
assert TestComp.template_file is None
assert GrandParentComp._template is None
assert ParentComp._template is None
assert isinstance(TestComp._template, Template)
assert TestComp._template.source == "<h1>child</h1>"
assert TestComp._template.origin.component_cls == TestComp
@djc_test @djc_test

View file

@ -3,7 +3,7 @@ from typing import Any, Callable, Dict, List, cast
import pytest import pytest
from django.http import HttpRequest, HttpResponse from django.http import HttpRequest, HttpResponse
from django.template import Context from django.template import Context, Origin, Template
from django.test import Client from django.test import Client
from django_components import Component, Slot, SlotNode, register, registry from django_components import Component, Slot, SlotNode, register, registry
@ -23,10 +23,15 @@ from django_components.extension import (
OnComponentDataContext, OnComponentDataContext,
OnComponentRenderedContext, OnComponentRenderedContext,
OnSlotRenderedContext, OnSlotRenderedContext,
OnTemplateLoadedContext,
OnTemplateCompiledContext,
OnJsLoadedContext,
OnCssLoadedContext,
) )
from django_components.extensions.cache import CacheExtension from django_components.extensions.cache import CacheExtension
from django_components.extensions.debug_highlight import DebugHighlightExtension from django_components.extensions.debug_highlight import DebugHighlightExtension
from django_components.extensions.defaults import DefaultsExtension from django_components.extensions.defaults import DefaultsExtension
from django_components.extensions.dependencies import DependenciesExtension
from django_components.extensions.view import ViewExtension from django_components.extensions.view import ViewExtension
from django_components.testing import djc_test from django_components.testing import djc_test
@ -85,6 +90,10 @@ class DummyExtension(ComponentExtension):
"on_component_data": [], "on_component_data": [],
"on_component_rendered": [], "on_component_rendered": [],
"on_slot_rendered": [], "on_slot_rendered": [],
"on_template_loaded": [],
"on_template_compiled": [],
"on_js_loaded": [],
"on_css_loaded": [],
} }
urls = [ urls = [
@ -126,6 +135,18 @@ class DummyExtension(ComponentExtension):
def on_slot_rendered(self, ctx: OnSlotRenderedContext) -> None: def on_slot_rendered(self, ctx: OnSlotRenderedContext) -> None:
self.calls["on_slot_rendered"].append(ctx) self.calls["on_slot_rendered"].append(ctx)
def on_template_loaded(self, ctx):
self.calls["on_template_loaded"].append(ctx)
def on_template_compiled(self, ctx):
self.calls["on_template_compiled"].append(ctx)
def on_js_loaded(self, ctx):
self.calls["on_js_loaded"].append(ctx)
def on_css_loaded(self, ctx):
self.calls["on_css_loaded"].append(ctx)
class DummyNestedExtension(ComponentExtension): class DummyNestedExtension(ComponentExtension):
name = "test_nested_extension" name = "test_nested_extension"
@ -182,16 +203,30 @@ def with_registry(on_created: Callable):
on_created(registry) on_created(registry)
class OverrideAssetExtension(ComponentExtension):
name = "override_asset_extension"
def on_template_loaded(self, ctx):
return "OVERRIDDEN TEMPLATE"
def on_js_loaded(self, ctx):
return "OVERRIDDEN JS"
def on_css_loaded(self, ctx):
return "OVERRIDDEN CSS"
@djc_test @djc_test
class TestExtension: class TestExtension:
@djc_test(components_settings={"extensions": [DummyExtension]}) @djc_test(components_settings={"extensions": [DummyExtension]})
def test_extensions_setting(self): def test_extensions_setting(self):
assert len(app_settings.EXTENSIONS) == 5 assert len(app_settings.EXTENSIONS) == 6
assert isinstance(app_settings.EXTENSIONS[0], CacheExtension) assert isinstance(app_settings.EXTENSIONS[0], CacheExtension)
assert isinstance(app_settings.EXTENSIONS[1], DefaultsExtension) assert isinstance(app_settings.EXTENSIONS[1], DefaultsExtension)
assert isinstance(app_settings.EXTENSIONS[2], ViewExtension) assert isinstance(app_settings.EXTENSIONS[2], DependenciesExtension)
assert isinstance(app_settings.EXTENSIONS[3], DebugHighlightExtension) assert isinstance(app_settings.EXTENSIONS[3], ViewExtension)
assert isinstance(app_settings.EXTENSIONS[4], DummyExtension) assert isinstance(app_settings.EXTENSIONS[4], DebugHighlightExtension)
assert isinstance(app_settings.EXTENSIONS[5], DummyExtension)
@djc_test(components_settings={"extensions": [DummyExtension]}) @djc_test(components_settings={"extensions": [DummyExtension]})
def test_access_component_from_extension(self): def test_access_component_from_extension(self):
@ -230,7 +265,7 @@ class TestExtension:
class TestExtensionHooks: class TestExtensionHooks:
@djc_test(components_settings={"extensions": [DummyExtension]}) @djc_test(components_settings={"extensions": [DummyExtension]})
def test_component_class_lifecycle_hooks(self): def test_component_class_lifecycle_hooks(self):
extension = cast(DummyExtension, app_settings.EXTENSIONS[4]) extension = cast(DummyExtension, app_settings.EXTENSIONS[5])
assert len(extension.calls["on_component_class_created"]) == 0 assert len(extension.calls["on_component_class_created"]) == 0
assert len(extension.calls["on_component_class_deleted"]) == 0 assert len(extension.calls["on_component_class_deleted"]) == 0
@ -262,7 +297,7 @@ class TestExtensionHooks:
@djc_test(components_settings={"extensions": [DummyExtension]}) @djc_test(components_settings={"extensions": [DummyExtension]})
def test_registry_lifecycle_hooks(self): def test_registry_lifecycle_hooks(self):
extension = cast(DummyExtension, app_settings.EXTENSIONS[4]) extension = cast(DummyExtension, app_settings.EXTENSIONS[5])
assert len(extension.calls["on_registry_created"]) == 0 assert len(extension.calls["on_registry_created"]) == 0
assert len(extension.calls["on_registry_deleted"]) == 0 assert len(extension.calls["on_registry_deleted"]) == 0
@ -299,7 +334,7 @@ class TestExtensionHooks:
return {"name": kwargs.get("name", "World")} return {"name": kwargs.get("name", "World")}
registry.register("test_comp", TestComponent) registry.register("test_comp", TestComponent)
extension = cast(DummyExtension, app_settings.EXTENSIONS[4]) extension = cast(DummyExtension, app_settings.EXTENSIONS[5])
# Verify on_component_registered was called # Verify on_component_registered was called
assert len(extension.calls["on_component_registered"]) == 1 assert len(extension.calls["on_component_registered"]) == 1
@ -337,7 +372,7 @@ class TestExtensionHooks:
test_slots = {"content": "Some content"} test_slots = {"content": "Some content"}
TestComponent.render(context=test_context, args=("arg1", "arg2"), kwargs={"name": "Test"}, slots=test_slots) TestComponent.render(context=test_context, args=("arg1", "arg2"), kwargs={"name": "Test"}, slots=test_slots)
extension = cast(DummyExtension, app_settings.EXTENSIONS[4]) extension = cast(DummyExtension, app_settings.EXTENSIONS[5])
# Verify on_component_input was called with correct args # Verify on_component_input was called with correct args
assert len(extension.calls["on_component_input"]) == 1 assert len(extension.calls["on_component_input"]) == 1
@ -386,7 +421,7 @@ class TestExtensionHooks:
slots={"content": "Some content"}, slots={"content": "Some content"},
) )
extension = cast(DummyExtension, app_settings.EXTENSIONS[4]) extension = cast(DummyExtension, app_settings.EXTENSIONS[5])
# Verify on_component_rendered was called with correct args # Verify on_component_rendered was called with correct args
assert len(extension.calls["on_component_rendered"]) == 1 assert len(extension.calls["on_component_rendered"]) == 1
@ -415,7 +450,7 @@ class TestExtensionHooks:
assert rendered == "Hello Some content!" assert rendered == "Hello Some content!"
extension = cast(DummyExtension, app_settings.EXTENSIONS[4]) extension = cast(DummyExtension, app_settings.EXTENSIONS[5])
# Verify on_slot_rendered was called with correct args # Verify on_slot_rendered was called with correct args
assert len(extension.calls["on_slot_rendered"]) == 1 assert len(extension.calls["on_slot_rendered"]) == 1
@ -469,6 +504,120 @@ class TestExtensionHooks:
rendered = TestComponent.render(args=(), kwargs={"name": "Test"}) rendered = TestComponent.render(args=(), kwargs={"name": "Test"})
assert rendered == "<div>OVERRIDDEN: Hello Test!</div>" assert rendered == "<div>OVERRIDDEN: Hello Test!</div>"
@djc_test(components_settings={"extensions": [DummyExtension]})
def test_asset_hooks__inlined(self):
@register("test_comp_hooks")
class TestComponent(Component):
template = "Hello {{ name }}!"
js = "console.log('hi');"
css = "body { color: red; }"
def get_template_data(self, args, kwargs, slots, context):
return {"name": kwargs.get("name", "World")}
# Render the component to trigger all hooks
TestComponent.render(args=(), kwargs={"name": "Test"})
extension = cast(DummyExtension, app_settings.EXTENSIONS[5])
# on_template_loaded
assert len(extension.calls["on_template_loaded"]) == 1
ctx1: OnTemplateLoadedContext = extension.calls["on_template_loaded"][0]
assert ctx1.component_cls == TestComponent
assert ctx1.content == "Hello {{ name }}!"
assert isinstance(ctx1.origin, Origin)
assert ctx1.origin.name.endswith("test_extension.py::TestComponent")
assert ctx1.name is None
# on_template_compiled
assert len(extension.calls["on_template_compiled"]) == 1
ctx2: OnTemplateCompiledContext = extension.calls["on_template_compiled"][0]
assert ctx2.component_cls == TestComponent
assert isinstance(ctx2.template, Template)
# on_js_loaded
assert len(extension.calls["on_js_loaded"]) == 1
ctx3: OnJsLoadedContext = extension.calls["on_js_loaded"][0]
assert ctx3.component_cls == TestComponent
assert ctx3.content == "console.log('hi');"
# on_css_loaded
assert len(extension.calls["on_css_loaded"]) == 1
ctx4: OnCssLoadedContext = extension.calls["on_css_loaded"][0]
assert ctx4.component_cls == TestComponent
assert ctx4.content == "body { color: red; }"
@djc_test(components_settings={"extensions": [DummyExtension]})
def test_asset_hooks__file(self):
@register("test_comp_hooks")
class TestComponent(Component):
template_file = "relative_file/relative_file.html"
js_file = "relative_file/relative_file.js"
css_file = "relative_file/relative_file.css"
def get_template_data(self, args, kwargs, slots, context):
return {"name": kwargs.get("name", "World")}
# Render the component to trigger all hooks
TestComponent.render(args=(), kwargs={"name": "Test"})
extension = cast(DummyExtension, app_settings.EXTENSIONS[5])
# on_template_loaded
# NOTE: The template file gets picked up by 'django.template.loaders.filesystem.Loader',
# as well as our own loader, so we get two calls here.
assert len(extension.calls["on_template_loaded"]) == 2
ctx1: OnTemplateLoadedContext = extension.calls["on_template_loaded"][0]
assert ctx1.component_cls == TestComponent
assert ctx1.content == (
'<form method="post">\n'
' {% csrf_token %}\n'
' <input type="text" name="variable" value="{{ variable }}">\n'
' <input type="submit">\n'
'</form>\n'
)
assert isinstance(ctx1.origin, Origin)
assert ctx1.origin.name.endswith("relative_file.html")
assert ctx1.name == "relative_file/relative_file.html"
# on_template_compiled
assert len(extension.calls["on_template_compiled"]) == 2
ctx2: OnTemplateCompiledContext = extension.calls["on_template_compiled"][0]
assert ctx2.component_cls == TestComponent
assert isinstance(ctx2.template, Template)
# on_js_loaded
assert len(extension.calls["on_js_loaded"]) == 1
ctx3: OnJsLoadedContext = extension.calls["on_js_loaded"][0]
assert ctx3.component_cls == TestComponent
assert ctx3.content == 'console.log("JS file");\n'
# on_css_loaded
assert len(extension.calls["on_css_loaded"]) == 1
ctx4: OnCssLoadedContext = extension.calls["on_css_loaded"][0]
assert ctx4.component_cls == TestComponent
assert ctx4.content == (
'.html-css-only {\n'
' color: blue;\n'
'}\n'
)
@djc_test(components_settings={"extensions": [OverrideAssetExtension]})
def test_asset_hooks_override(self):
@register("test_comp_override")
class TestComponent(Component):
template = "Hello {{ name }}!"
js = "console.log('hi');"
css = "body { color: red; }"
def get_template_data(self, args, kwargs, slots, context):
return {"name": kwargs.get("name", "World")}
# No need to render, accessing the attributes should trigger the hooks
assert TestComponent.template == "OVERRIDDEN TEMPLATE"
assert TestComponent.js == "OVERRIDDEN JS"
assert TestComponent.css == "OVERRIDDEN CSS"
@djc_test @djc_test
class TestExtensionViews: class TestExtensionViews:

View file

@ -96,3 +96,19 @@ class TestTemplateSignal:
templates_used = _get_templates_used_to_render(template) templates_used = _get_templates_used_to_render(template)
assert "slotted_template.html" in templates_used assert "slotted_template.html" in templates_used
assert "simple_template.html" in templates_used assert "simple_template.html" in templates_used
@djc_test(parametrize=PARAMETRIZE_CONTEXT_BEHAVIOR)
@with_template_signal
def test_template_rendered_skipped_when_no_template(self, components_settings):
class EmptyComponent(Component):
pass
registry.register("empty", EmptyComponent)
template_str: types.django_html = """
{% load component_tags %}
{% component 'empty' / %}
"""
template = Template(template_str, name="root")
templates_used = _get_templates_used_to_render(template)
assert templates_used == ["root"]

View file

@ -25,6 +25,17 @@ def gen_blocked_and_slotted_component():
return BlockedAndSlottedComponent return BlockedAndSlottedComponent
def gen_component_inside_include():
class ComponentInsideInclude(Component):
template: types.django_html = """<div>Hello</div>"""
class Media:
css = "style.css"
js = "script.js"
return ComponentInsideInclude
####################### #######################
# TESTS # TESTS
####################### #######################
@ -536,6 +547,112 @@ class TestExtendsCompat:
""" """
assertHTMLEqual(rendered, expected) assertHTMLEqual(rendered, expected)
# In this case, `{% include %}` is NOT nested inside a `{% component %}` tag.
# We need to ensure that the component inside the `{% include %}` is rendered as if with deps_strategy="ignore",
# so the parent template decides how to render the JS/CSS.
# See https://github.com/django-components/django-components/issues/1296
@djc_test(parametrize=PARAMETRIZE_CONTEXT_BEHAVIOR)
def test_component_with_media_inside_include(self, components_settings):
registry.register("test_component", gen_component_inside_include())
template: types.django_html = """
{% load component_tags %}
<body>
<outer>
{% include "component_inside_include_sub.html" %}
</outer>
</body>
"""
rendered_raw = Template(template).render(Context({"DJC_DEPS_STRATEGY": "ignore"}))
expected_raw = """
<body>
<outer>
<div data-djc-id-ca1bc3f>Hello</div>
</outer>
</body>
"""
assertHTMLEqual(rendered_raw, expected_raw)
template_obj = Template(template)
context = Context()
rendered = template_obj.render(context)
# NOTE: It's important that the <script> tags are rendered outside of <div> and <outer> tags,
# because that tells us that the JS/CSS is rendered by the parent template, not the component
# inside the include.
expected = """
<body>
<outer>
<div data-djc-id-ca1bc41>Hello</div>
</outer>
<script src="django_components/django_components.min.js"></script>
<script type="application/json" data-djc>{"loadedCssUrls": ["c3R5bGUuY3Nz"],
"loadedJsUrls": ["c2NyaXB0Lmpz"],
"toLoadCssTags": [],
"toLoadJsTags": []}</script>
<script src="script.js"></script>
</body>
"""
assertHTMLEqual(rendered, expected)
# In this case, because `{% include %}` is rendered inside a `{% component %}` tag,
# then the component inside the `{% include %}` knows it's inside another component.
# So it's always rendered as if with deps_strategy="ignore".
# See https://github.com/django-components/django-components/issues/1296
@djc_test(parametrize=PARAMETRIZE_CONTEXT_BEHAVIOR)
def test_component_with_media_inside_include_inside_component(self, components_settings):
registry.register("test_component", gen_component_inside_include())
@register("component_inside_include")
class CompInsideIncludeComponent(Component):
template: types.django_html = """
<body>
<outer>
{% include "component_inside_include_sub.html" %}
</outer>
</body>
"""
template: types.django_html = """
{% load component_tags %}
<html>
{% component "component_inside_include" / %}
</html>
"""
rendered_raw = Template(template).render(Context({"DJC_DEPS_STRATEGY": "ignore"}))
expected_raw = """
<html>
<body data-djc-id-ca1bc3f>
<outer>
<div data-djc-id-ca1bc41>Hello</div>
</outer>
</body>
</html>
"""
assertHTMLEqual(rendered_raw, expected_raw)
template_obj = Template(template)
context = Context()
rendered = template_obj.render(context)
expected = """
<html>
<body data-djc-id-ca1bc43>
<outer>
<div data-djc-id-ca1bc45>Hello</div>
</outer>
<script src="django_components/django_components.min.js"></script>
<script type="application/json" data-djc>{"loadedCssUrls": ["c3R5bGUuY3Nz"],
"loadedJsUrls": ["c2NyaXB0Lmpz"],
"toLoadCssTags": [],
"toLoadJsTags": []}</script>
<script src="script.js"></script>
</body>
</html>
"""
assertHTMLEqual(rendered, expected)
@djc_test(parametrize=PARAMETRIZE_CONTEXT_BEHAVIOR) @djc_test(parametrize=PARAMETRIZE_CONTEXT_BEHAVIOR)
def test_component_inside_block(self, components_settings): def test_component_inside_block(self, components_settings):
registry.register("slotted_component", gen_slotted_component()) registry.register("slotted_component", gen_slotted_component())