mirror of
https://github.com/django-components/django-components.git
synced 2025-08-05 06:48:00 +00:00
Compare commits
75 commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
09e96d92d6 | ||
![]() |
df8c6ab1a4 | ||
![]() |
10e1a25b26 | ||
![]() |
9e280f2e61 | ||
![]() |
de1ebcd071 | ||
![]() |
740a8c5f1c | ||
![]() |
7a260f7e47 | ||
![]() |
6772736f4c | ||
![]() |
8246f908bf | ||
![]() |
5d7e235725 | ||
![]() |
2a44fbec27 | ||
![]() |
f4347dd9d9 | ||
![]() |
65373ec3e8 | ||
![]() |
81c0d419b4 | ||
![]() |
672811b8b4 | ||
![]() |
f8c8292441 | ||
![]() |
8cba8702a3 | ||
![]() |
fb4b654433 | ||
![]() |
5f8ec71358 | ||
![]() |
f15f1e967a | ||
![]() |
b7b0d250c4 | ||
![]() |
0c7e17d8cf | ||
![]() |
514b8206b5 | ||
![]() |
bcc7bac48d | ||
![]() |
39e9ee0dc3 | ||
![]() |
df7f94cb2d | ||
![]() |
9a4f835201 | ||
![]() |
59e5fb3b38 | ||
![]() |
c692b7a310 | ||
![]() |
007009a480 | ||
![]() |
f8e2721244 | ||
![]() |
77fd149346 | ||
![]() |
055dd07694 | ||
![]() |
e7883d0b54 | ||
![]() |
64322a7b84 | ||
![]() |
b021b54ad4 | ||
![]() |
212336c99a | ||
![]() |
8ceb143fb3 | ||
![]() |
15669a2fb1 | ||
![]() |
10a9e555c0 | ||
![]() |
4d9c814e32 | ||
![]() |
68089daefb | ||
![]() |
b0da3b166d | ||
![]() |
5e34ec28cf | ||
![]() |
b0ef1f2a4c | ||
![]() |
14a8358b08 | ||
![]() |
b09c83b128 | ||
![]() |
6e86851894 | ||
![]() |
b9e8b8e569 | ||
![]() |
1db72b6e96 | ||
![]() |
cd1643ad23 | ||
![]() |
6d754acb99 | ||
![]() |
50d71b6187 | ||
![]() |
bdfe966919 | ||
![]() |
d537ba1ee5 | ||
![]() |
06c89cf9e8 | ||
![]() |
3fc96daa9b | ||
![]() |
458e1894db | ||
![]() |
2350a6b6c4 | ||
![]() |
291b97cfd1 | ||
![]() |
6a8fc27bad | ||
![]() |
328b55ff29 | ||
![]() |
136a7a591c | ||
![]() |
4d056b2c49 | ||
![]() |
8209614fc2 | ||
![]() |
7bece01024 | ||
![]() |
49f0c05bf6 | ||
![]() |
831db00598 | ||
![]() |
09bcf8dbcc | ||
![]() |
efe5eb0ba5 | ||
![]() |
ace29db9ac | ||
![]() |
04737412d9 | ||
![]() |
b2e053118b | ||
![]() |
7b24b86f4a | ||
![]() |
468abaf92f |
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
32
.devcontainer/devcontainer.json
Normal file
32
.devcontainer/devcontainer.json
Normal 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": ""
|
||||
}
|
88
CHANGELOG.md
88
CHANGELOG.md
|
@ -1,5 +1,77 @@
|
|||
# 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
|
||||
|
||||
⚠️ Major release ⚠️ - Please test thoroughly before / after upgrading.
|
||||
|
@ -64,7 +136,7 @@ Summary:
|
|||
- `"append"`
|
||||
- `"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**
|
||||
|
||||
|
@ -88,8 +160,8 @@ Summary:
|
|||
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.
|
||||
|
||||
```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()`.
|
||||
- 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.
|
||||
|
||||
|
@ -1057,7 +1129,7 @@ Summary:
|
|||
- Modify the rendered output after it has been rendered
|
||||
- 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.
|
||||
|
||||
|
@ -1125,7 +1197,7 @@ Summary:
|
|||
- `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.
|
||||
|
||||
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.
|
||||
|
||||
|
@ -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()`
|
||||
|
||||
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:
|
||||
|
||||
|
@ -1242,7 +1314,7 @@ Summary:
|
|||
|
||||
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
|
||||
Mypy errors, because Mypy doesn't allow using class attributes as bases:
|
||||
|
|
|
@ -24,8 +24,9 @@ A component in django-components can be as simple as a Django template and Pytho
|
|||
|
||||
```py
|
||||
# components/calendar/calendar.py
|
||||
from django_components import Component
|
||||
from django_components import Component, register
|
||||
|
||||
@register("calendar")
|
||||
class Calendar(Component):
|
||||
template_file = "calendar.html"
|
||||
```
|
||||
|
@ -56,8 +57,9 @@ document.querySelector(".calendar").onclick = () => {
|
|||
|
||||
```py
|
||||
# components/calendar/calendar.py
|
||||
from django_components import Component
|
||||
from django_components import Component, register
|
||||
|
||||
@register("calendar")
|
||||
class Calendar(Component):
|
||||
template_file = "calendar.html"
|
||||
js_file = "calendar.js"
|
||||
|
@ -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.
|
||||
|
||||
- [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/).
|
||||
|
||||
|
|
|
@ -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]]]
|
|
@ -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]]]
|
|
@ -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]]]
|
|
@ -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]]]
|
|
@ -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]]]
|
|
@ -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]]]
|
|
@ -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]]]
|
|
@ -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]]]
|
|
@ -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]]]
|
|
@ -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]]
|
|
@ -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]]]
|
|
@ -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]]]
|
|
@ -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]]]
|
|
@ -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]]]
|
|
@ -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]]]
|
|
@ -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]]]
|
|
@ -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]]]
|
|
@ -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]]]
|
|
@ -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]]]
|
File diff suppressed because one or more lines are too long
|
@ -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]]
|
|
@ -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]]
|
|
@ -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]]
|
|
@ -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]]
|
|
@ -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]]
|
|
@ -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]]
|
|
@ -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]]
|
|
@ -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]]
|
|
@ -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]]
|
|
@ -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]]
|
|
@ -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]]
|
|
@ -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]]
|
|
@ -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]]
|
|
@ -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]]
|
|
@ -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]]
|
|
@ -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]]
|
|
@ -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]]
|
|
@ -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]]
|
|
@ -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
|
@ -1,4 +1,4 @@
|
|||
{
|
||||
"asv-version": "0.6.4",
|
||||
"timestamp": 1745143636166
|
||||
"timestamp": 1753049912703
|
||||
}
|
|
@ -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]]]]}
|
|
@ -1,8 +1,8 @@
|
|||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<feed xmlns="http://www.w3.org/2005/Atom"><id>tag:django-components.asv,1970-01-01:/cddbdcca8b398afd301fbfc73cc4d51103d4e3059c0e6b938d4c467ad3d1aa25</id><author><name>Airspeed Velocity</name></author><title xml:lang="en">django-components performance regressions</title><updated>2025-03-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&commits=42818ad6ffb47bd650d8a379b84c3d48394f9f77-a6455d70f6c28ddbd4be8e58902f6cbc101e5ff3" /><content xml:lang="en" type="html"><a href="index.html#Components vs Django.timeraw_render_sm_subsequent?p-renderer=%27django%27&commits=42818ad6ffb47bd650d8a379b84c3d48394f9f77-a6455d70f6c28ddbd4be8e58902f6cbc101e5ff3">15.75% regression</a> on 2025-03-31 14:10:42 in commits <a href="#42818ad6ffb47bd650d8a379b84c3d48394f9f77">42818ad6...a6455d70</a>.<br>
|
||||
New value: 116μs, old value: 101μs.<br>
|
||||
Latest value: 116μs (15.75% 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&commits=42818ad6ffb47bd650d8a379b84c3d48394f9f77-a6455d70f6c28ddbd4be8e58902f6cbc101e5ff3" /><content xml:lang="en" type="html"><a href="index.html#Components vs Django.timeraw_render_lg_subsequent?p-renderer=%27django%27&commits=42818ad6ffb47bd650d8a379b84c3d48394f9f77-a6455d70f6c28ddbd4be8e58902f6cbc101e5ff3">11.92% regression</a> on 2025-03-31 14:10:42 in commits <a href="#42818ad6ffb47bd650d8a379b84c3d48394f9f77">42818ad6...a6455d70</a>.<br>
|
||||
<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&commits=4c909486069f3c3c8ee7915239174f820f081da4-7b24b86f4a836c697acba926d9d6602afa45418d" /><content xml:lang="en" type="html"><a href="index.html#Components vs Django.timeraw_render_lg_subsequent?p-renderer=%27django%27&commits=4c909486069f3c3c8ee7915239174f820f081da4-7b24b86f4a836c697acba926d9d6602afa45418d">17.14% regression</a> on 2025-06-04 22:35:21 in commits <a href="#4c909486069f3c3c8ee7915239174f820f081da4">4c909486...7b24b86f</a>.<br>
|
||||
New value: 43.6ms, old value: 37.2ms.<br>
|
||||
Latest value: 43.6ms (31.10% worse
|
||||
than best value 33.3ms).</content></entry><entry><id>tag:django-components.asv,2025-03-31:/7a13128cbc4d175ca09ebda40e8a303789275bd84b00a5d496cfa08a26ad2f8b</id><title xml:lang="en">11.92% Components vs Django.timeraw_render_lg_subsequent('django')</title><updated>2025-03-31T14:16:53Z</updated><link href="index.html#Components vs Django.timeraw_render_lg_subsequent?p-renderer=%27django%27&commits=42818ad6ffb47bd650d8a379b84c3d48394f9f77-a6455d70f6c28ddbd4be8e58902f6cbc101e5ff3" /><content xml:lang="en" type="html"><a href="index.html#Components vs Django.timeraw_render_lg_subsequent?p-renderer=%27django%27&commits=42818ad6ffb47bd650d8a379b84c3d48394f9f77-a6455d70f6c28ddbd4be8e58902f6cbc101e5ff3">11.92% regression</a> on 2025-03-31 14:10:42 in commits <a href="#42818ad6ffb47bd650d8a379b84c3d48394f9f77">42818ad6...a6455d70</a>.<br>
|
||||
New value: 37.2ms, old value: 33.3ms.<br>
|
||||
Latest value: 37.2ms (11.92% worse
|
||||
Latest value: 43.6ms (31.10% worse
|
||||
than best value 33.3ms).</content></entry></feed>
|
|
@ -134,7 +134,7 @@ Which will render as:
|
|||
{% endcomponent %}
|
||||
```
|
||||
|
||||
### 5. Wait, there's a bug
|
||||
### 4. Wait, there's a bug
|
||||
|
||||
There is a mistake in our code! `2024-12-13` is Friday, so that's fine. But if we updated
|
||||
the to `2024-12-14`, which is Saturday, our template from previous step would render this:
|
||||
|
@ -289,3 +289,8 @@ each time:
|
|||
|
||||
Moreover, slots are treated as part of the template - for example the CSS scoping (work in progress)
|
||||
is applied to the slot content too.
|
||||
|
||||
---
|
||||
|
||||
So far we've rendered components using template tag. [Next, let’s explore other ways to render components ➡️]
|
||||
(./rendering_components.md)
|
||||
|
|
|
@ -55,7 +55,7 @@ by calling `{% load component_tags %}` inside the template.
|
|||
like `{% component "calendar" / %}`.
|
||||
|
||||
`ComponentRegistries` also make it possible to group and share components as standalone packages.
|
||||
[Learn more here](../../concepts/advanced/authoring_component_libraries).
|
||||
[Learn more here](../../concepts/advanced/component_libraries).
|
||||
|
||||
!!! note
|
||||
|
||||
|
|
|
@ -220,10 +220,6 @@ the parametrized version of the component:
|
|||
</div>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
Next, you will learn [how to use slots give your components even more flexibility ➡️](./adding_slots.md)
|
||||
|
||||
### 5. Add defaults
|
||||
|
||||
In our example, we've set the `extra_class` to default to `"text-blue"` by setting it in the
|
||||
|
@ -258,3 +254,7 @@ class Calendar(Component):
|
|||
"extra_class": kwargs["extra_class"], # <--- changed
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
Next, you will learn [how to use slots give your components even more flexibility ➡️](./adding_slots.md)
|
||||
|
|
|
@ -160,7 +160,7 @@ def my_view(request):
|
|||
response_class = MyCustomResponse
|
||||
```
|
||||
|
||||
### Rendering slots
|
||||
### 4. Rendering slots
|
||||
|
||||
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)
|
||||
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).
|
||||
|
||||
|
|
|
@ -35,8 +35,9 @@ document.querySelector(".calendar").onclick = function () {
|
|||
```
|
||||
|
||||
```py title="calendar.py"
|
||||
from django_components import Component
|
||||
from django_components import Component, register
|
||||
|
||||
@register("calendar")
|
||||
class Calendar(Component):
|
||||
template_file = "calendar.html"
|
||||
js_file = "calendar.js"
|
||||
|
|
|
@ -7,9 +7,9 @@ Please, before opening a new discussion, [check if similar discussion wasn't ope
|
|||
## Community examples
|
||||
|
||||
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.
|
||||
|
||||
- [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/).
|
||||
|
|
|
@ -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"
|
||||
from django_components import Component
|
||||
from django_components import Component, register
|
||||
|
||||
@register("calendar")
|
||||
class Calendar(Component):
|
||||
template_file = "calendar.html"
|
||||
```
|
||||
|
@ -46,8 +47,9 @@ document.querySelector(".calendar").onclick = () => {
|
|||
```
|
||||
|
||||
```py title="components/calendar/calendar.py"
|
||||
from django_components import Component
|
||||
from django_components import Component, register
|
||||
|
||||
@register("calendar")
|
||||
class Calendar(Component):
|
||||
template_file = "calendar.html"
|
||||
js_file = "calendar.js"
|
||||
|
|
|
@ -1,16 +1,16 @@
|
|||
# `.nav.yml` is provided by https://lukasgeiter.github.io/mkdocs-awesome-nav
|
||||
nav:
|
||||
- API: api.md
|
||||
- Commands: commands.md
|
||||
- CLI commands: commands.md
|
||||
- Components: components.md
|
||||
- Exceptions: exceptions.md
|
||||
- Extension commands: extension_commands.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
|
||||
- Signals: signals.md
|
||||
- Tag formatters: tag_formatters.md
|
||||
- Template tags: template_tags.md
|
||||
- Template vars: template_vars.md
|
||||
- Template variables: template_variables.md
|
||||
- URLs: urls.md
|
||||
- Testing API: testing_api.md
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<!-- Autogenerated by reference.py -->
|
||||
|
||||
# Extension commands
|
||||
# Extension commands API
|
||||
|
||||
Overview of all classes, functions, and other objects related to defining extension commands.
|
||||
|
||||
|
|
|
@ -148,6 +148,42 @@ name | type | description
|
|||
`name` | `str` | The name the component was registered under
|
||||
`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
|
||||
options:
|
||||
heading_level: 3
|
||||
|
@ -207,6 +243,44 @@ name | type | description
|
|||
`slot_name` | `str` | The name 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
|
||||
|
||||
::: django_components.extension.OnComponentClassCreatedContext
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<!-- Autogenerated by reference.py -->
|
||||
|
||||
# Extension URLs
|
||||
# Extension URLs API
|
||||
|
||||
Overview of all classes, functions, and other objects related to defining extension URLs.
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -44,6 +44,7 @@ from pathlib import Path
|
|||
from textwrap import dedent
|
||||
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.urls import URLPattern, URLResolver
|
||||
|
||||
|
@ -465,7 +466,13 @@ def gen_reference_commands():
|
|||
# Add link to source code
|
||||
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]
|
||||
obj_lineno = inspect.findsource(cmd_def_cls)[1]
|
||||
|
||||
# NOTE: Raises `OSError` if the file is not found.
|
||||
try:
|
||||
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)
|
||||
|
||||
# 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,
|
||||
like `{% slot %}`, `{% component %}`, etc.
|
||||
|
@ -537,7 +544,7 @@ def gen_reference_templatetags():
|
|||
]
|
||||
|
||||
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.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
|
||||
under the `{{ component_vars }}` variable, as defined by `ComponentVars`.
|
||||
"""
|
||||
preface = "<!-- Autogenerated by reference.py -->\n\n"
|
||||
preface += (root / "docs/templates/reference_templatevars.md").read_text()
|
||||
out_file = root / "docs/reference/template_vars.md"
|
||||
preface += (root / "docs/templates/reference_template_variables.md").read_text()
|
||||
out_file = root / "docs/reference/template_variables.md"
|
||||
|
||||
out_file.parent.mkdir(parents=True, exist_ok=True)
|
||||
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():
|
||||
"""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_exceptions()
|
||||
gen_reference_components()
|
||||
|
@ -1106,8 +1120,8 @@ def gen_reference():
|
|||
gen_reference_tagformatters()
|
||||
gen_reference_urls()
|
||||
gen_reference_commands()
|
||||
gen_reference_templatetags()
|
||||
gen_reference_templatevars()
|
||||
gen_reference_template_tags()
|
||||
gen_reference_template_variables()
|
||||
gen_reference_signals()
|
||||
gen_reference_testing_api()
|
||||
gen_reference_extension_hooks()
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Extension commands
|
||||
# Extension commands API
|
||||
|
||||
Overview of all classes, functions, and other objects related to defining extension commands.
|
||||
|
||||
|
|
2
docs/templates/reference_extension_urls.md
vendored
2
docs/templates/reference_extension_urls.md
vendored
|
@ -1,4 +1,4 @@
|
|||
# Extension URLs
|
||||
# Extension URLs API
|
||||
|
||||
Overview of all classes, functions, and other objects related to defining extension URLs.
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|||
|
||||
[project]
|
||||
name = "django_components"
|
||||
version = "0.140.0"
|
||||
version = "0.141.2"
|
||||
requires-python = ">=3.8, <4.0"
|
||||
description = "A way to create simple reusable template components in Django."
|
||||
keywords = ["django", "components", "css", "js", "html"]
|
||||
|
|
|
@ -7,7 +7,7 @@ whitenoise
|
|||
asv
|
||||
# NOTE: pin virtualenv to <20.31 until asv fixes integration
|
||||
# See https://github.com/airspeed-velocity/asv/issues/1484
|
||||
virtualenv==20.31.2
|
||||
virtualenv==20.32.0
|
||||
pytest-asyncio
|
||||
pytest-django
|
||||
typing-extensions>=4.12.2
|
||||
|
|
|
@ -104,7 +104,7 @@ urllib3==2.2.3
|
|||
# via
|
||||
# requests
|
||||
# types-requests
|
||||
virtualenv==20.31.2
|
||||
virtualenv==20.32.0
|
||||
# via
|
||||
# -r requirements-ci.in
|
||||
# asv
|
||||
|
|
|
@ -20,6 +20,6 @@ pygments-djc
|
|||
asv
|
||||
# NOTE: pin virtualenv to <20.31 until asv fixes integration
|
||||
# See https://github.com/airspeed-velocity/asv/issues/1484
|
||||
virtualenv==20.31.2
|
||||
virtualenv==20.32.0
|
||||
typing-extensions>=4.12.2
|
||||
pathspec
|
|
@ -30,7 +30,7 @@ colorama==0.4.6
|
|||
# via tox
|
||||
distlib==0.3.9
|
||||
# via virtualenv
|
||||
django==4.2.21
|
||||
django==4.2.23
|
||||
# via -r requirements-dev.in
|
||||
djc-core-html-parser==1.0.2
|
||||
# via -r requirements-dev.in
|
||||
|
@ -40,7 +40,7 @@ filelock==3.16.1
|
|||
# via
|
||||
# tox
|
||||
# virtualenv
|
||||
flake8==7.2.0
|
||||
flake8==7.3.0
|
||||
# via
|
||||
# -r requirements-dev.in
|
||||
# flake8-pyproject
|
||||
|
@ -64,7 +64,7 @@ json5==0.10.0
|
|||
# via asv
|
||||
mccabe==0.7.0
|
||||
# via flake8
|
||||
mypy==1.16.0
|
||||
mypy==1.17.0
|
||||
# via -r requirements-dev.in
|
||||
mypy-extensions==1.0.0
|
||||
# via
|
||||
|
@ -97,11 +97,11 @@ pluggy==1.5.0
|
|||
# tox
|
||||
pre-commit==4.2.0
|
||||
# via -r requirements-dev.in
|
||||
pycodestyle==2.13.0
|
||||
pycodestyle==2.14.0
|
||||
# via flake8
|
||||
pyee==12.0.0
|
||||
# via playwright
|
||||
pyflakes==3.3.2
|
||||
pyflakes==3.4.0
|
||||
# via flake8
|
||||
pygments==2.19.1
|
||||
# via
|
||||
|
@ -163,7 +163,7 @@ urllib3==2.2.3
|
|||
# via
|
||||
# requests
|
||||
# types-requests
|
||||
virtualenv==20.31.2
|
||||
virtualenv==20.32.0
|
||||
# via
|
||||
# -r requirements-dev.in
|
||||
# asv
|
||||
|
|
|
@ -24,7 +24,7 @@
|
|||
# - djc-core-html-parser>=1.0
|
||||
#
|
||||
|
||||
asgiref==3.8.1
|
||||
asgiref==3.9.1
|
||||
# via django
|
||||
babel==2.17.0
|
||||
# via
|
||||
|
@ -32,13 +32,13 @@ babel==2.17.0
|
|||
# mkdocs-material
|
||||
black==25.1.0
|
||||
# via hatch.envs.docs
|
||||
bracex==2.5.post1
|
||||
bracex==2.6
|
||||
# via wcmatch
|
||||
cairocffi==1.7.1
|
||||
# via cairosvg
|
||||
cairosvg==2.8.2
|
||||
# via mkdocs-material
|
||||
certifi==2025.4.26
|
||||
certifi==2025.7.14
|
||||
# via requests
|
||||
cffi==1.17.1
|
||||
# via cairocffi
|
||||
|
@ -58,7 +58,7 @@ cssselect2==0.8.0
|
|||
# via cairosvg
|
||||
defusedxml==0.7.1
|
||||
# via cairosvg
|
||||
django==4.2.21
|
||||
django==4.2.23
|
||||
# via hatch.envs.docs
|
||||
djc-core-html-parser==1.0.2
|
||||
# via hatch.envs.docs
|
||||
|
@ -66,9 +66,9 @@ ghp-import==2.1.0
|
|||
# via mkdocs
|
||||
gitdb==4.0.12
|
||||
# via gitpython
|
||||
gitpython==3.1.44
|
||||
gitpython==3.1.45
|
||||
# via mkdocs-git-revision-date-localized-plugin
|
||||
griffe==1.7.3
|
||||
griffe==1.9.0
|
||||
# via mkdocstrings-python
|
||||
htmlmin2==0.1.13
|
||||
# via mkdocs-minify-plugin
|
||||
|
@ -86,14 +86,14 @@ jinja2==3.1.6
|
|||
# mkdocstrings
|
||||
jsmin==3.0.1
|
||||
# via mkdocs-minify-plugin
|
||||
markdown==3.8
|
||||
markdown==3.8.2
|
||||
# via
|
||||
# mkdocs
|
||||
# mkdocs-autorefs
|
||||
# mkdocs-material
|
||||
# mkdocstrings
|
||||
# pymdown-extensions
|
||||
markdown-exec==1.10.3
|
||||
markdown-exec==1.11.0
|
||||
# via hatch.envs.docs
|
||||
markupsafe==3.0.2
|
||||
# via
|
||||
|
@ -134,13 +134,13 @@ mkdocs-get-deps==0.2.0
|
|||
# via
|
||||
# mkdocs
|
||||
# mkdocstrings
|
||||
mkdocs-git-authors-plugin==0.9.5
|
||||
mkdocs-git-authors-plugin==0.10.0
|
||||
# via hatch.envs.docs
|
||||
mkdocs-git-revision-date-localized-plugin==1.4.7
|
||||
# via hatch.envs.docs
|
||||
mkdocs-include-markdown-plugin==7.1.5
|
||||
mkdocs-include-markdown-plugin==7.1.6
|
||||
# via hatch.envs.docs
|
||||
mkdocs-material==9.6.14
|
||||
mkdocs-material==9.6.15
|
||||
# via hatch.envs.docs
|
||||
mkdocs-material-extensions==1.3.1
|
||||
# via mkdocs-material
|
||||
|
@ -148,11 +148,11 @@ mkdocs-minify-plugin==0.8.0
|
|||
# via hatch.envs.docs
|
||||
mkdocs-redirects==1.2.2
|
||||
# via hatch.envs.docs
|
||||
mkdocstrings==0.29.1
|
||||
mkdocstrings==0.30.0
|
||||
# via
|
||||
# hatch.envs.docs
|
||||
# mkdocstrings-python
|
||||
mkdocstrings-python==1.16.11
|
||||
mkdocstrings-python==1.16.12
|
||||
# via hatch.envs.docs
|
||||
mypy-extensions==1.1.0
|
||||
# via black
|
||||
|
@ -166,7 +166,7 @@ pathspec==0.12.1
|
|||
# via
|
||||
# black
|
||||
# mkdocs
|
||||
pillow==11.2.1
|
||||
pillow==11.3.0
|
||||
# via
|
||||
# cairosvg
|
||||
# mkdocs-material
|
||||
|
@ -176,14 +176,14 @@ platformdirs==4.3.8
|
|||
# mkdocs-get-deps
|
||||
pycparser==2.22
|
||||
# via cffi
|
||||
pygments==2.19.1
|
||||
pygments==2.19.2
|
||||
# via
|
||||
# hatch.envs.docs
|
||||
# mkdocs-material
|
||||
# pygments-djc
|
||||
pygments-djc==1.0.1
|
||||
# via hatch.envs.docs
|
||||
pymdown-extensions==10.15
|
||||
pymdown-extensions==10.16.1
|
||||
# via
|
||||
# hatch.envs.docs
|
||||
# markdown-exec
|
||||
|
@ -208,7 +208,7 @@ pyyaml-env-tag==1.1
|
|||
# mkdocs
|
||||
regex==2024.11.6
|
||||
# via mkdocs-material
|
||||
requests==2.32.3
|
||||
requests==2.32.4
|
||||
# via mkdocs-material
|
||||
six==1.17.0
|
||||
# via python-dateutil
|
||||
|
@ -220,17 +220,17 @@ tinycss2==1.4.0
|
|||
# via
|
||||
# cairosvg
|
||||
# cssselect2
|
||||
urllib3==2.4.0
|
||||
urllib3==2.5.0
|
||||
# via requests
|
||||
verspec==0.1.0
|
||||
# via mike
|
||||
watchdog==6.0.0
|
||||
# via mkdocs
|
||||
wcmatch==10.0
|
||||
wcmatch==10.1
|
||||
# via mkdocs-include-markdown-plugin
|
||||
webencodings==0.5.1
|
||||
# via
|
||||
# cssselect2
|
||||
# tinycss2
|
||||
zipp==3.22.0
|
||||
zipp==3.23.0
|
||||
# via importlib-metadata
|
||||
|
|
|
@ -746,8 +746,8 @@ defaults = ComponentsSettings(
|
|||
#
|
||||
# Settings are loaded from Django settings only once, at `apps.py` in `ready()`.
|
||||
class InternalSettings:
|
||||
def __init__(self, settings: Optional[Dict[str, Any]] = None):
|
||||
self._settings = ComponentsSettings(**settings) if settings else defaults
|
||||
def __init__(self) -> None:
|
||||
self._settings: Optional[ComponentsSettings] = None
|
||||
|
||||
def _load_settings(self) -> None:
|
||||
data = getattr(settings, "COMPONENTS", {})
|
||||
|
@ -786,6 +786,11 @@ class InternalSettings:
|
|||
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"]:
|
||||
extensions: Sequence[Union[Type["ComponentExtension"], str]] = default(
|
||||
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.debug_highlight import DebugHighlightExtension
|
||||
from django_components.extensions.defaults import DefaultsExtension
|
||||
from django_components.extensions.dependencies import DependenciesExtension
|
||||
from django_components.extensions.view import ViewExtension
|
||||
|
||||
extensions = cast(
|
||||
|
@ -802,6 +808,7 @@ class InternalSettings:
|
|||
[
|
||||
CacheExtension,
|
||||
DefaultsExtension,
|
||||
DependenciesExtension,
|
||||
ViewExtension,
|
||||
DebugHighlightExtension,
|
||||
],
|
||||
|
@ -853,70 +860,73 @@ class InternalSettings:
|
|||
|
||||
return raw_value
|
||||
|
||||
# TODO REMOVE THE PROPERTIES BELOW? THEY NO LONGER SERVE ANY PURPOSE
|
||||
@property
|
||||
def AUTODISCOVER(self) -> bool:
|
||||
return self._settings.autodiscover # type: ignore[return-value]
|
||||
return self._get_settings().autodiscover # type: ignore[return-value]
|
||||
|
||||
@property
|
||||
def CACHE(self) -> Optional[str]:
|
||||
return self._settings.cache
|
||||
return self._get_settings().cache
|
||||
|
||||
@property
|
||||
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
|
||||
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
|
||||
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
|
||||
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
|
||||
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
|
||||
def LIBRARIES(self) -> List[str]:
|
||||
return self._settings.libraries # type: ignore[return-value]
|
||||
return self._get_settings().libraries # type: ignore[return-value]
|
||||
|
||||
@property
|
||||
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
|
||||
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
|
||||
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
|
||||
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
|
||||
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
|
||||
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
|
||||
def CONTEXT_BEHAVIOR(self) -> ContextBehavior:
|
||||
return ContextBehavior(self._settings.context_behavior)
|
||||
return ContextBehavior(self._get_settings().context_behavior)
|
||||
|
||||
@property
|
||||
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()
|
||||
|
|
|
@ -4,6 +4,7 @@ from typing import Any
|
|||
|
||||
from django.apps import AppConfig
|
||||
from django.template import Template
|
||||
from django.template.loader_tags import IncludeNode
|
||||
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.components.dynamic import DynamicComponent
|
||||
from django_components.extension import extensions
|
||||
from django_components.util.django_monkeypatch import monkeypatch_template_cls
|
||||
|
||||
app_settings._load_settings()
|
||||
from django_components.util.django_monkeypatch import monkeypatch_include_node, monkeypatch_template_cls
|
||||
|
||||
# NOTE: This monkeypatch is applied here, before Django processes any requests.
|
||||
# To make django-components work with django-debug-toolbar-template-profiler
|
||||
# See https://github.com/django-components/django-components/discussions/819
|
||||
monkeypatch_template_cls(Template)
|
||||
monkeypatch_include_node(IncludeNode)
|
||||
|
||||
# Import modules set in `COMPONENTS.libraries` setting
|
||||
import_libraries()
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import sys
|
||||
from typing import Optional
|
||||
|
||||
from django.core.cache import BaseCache, caches
|
||||
|
@ -36,9 +37,14 @@ def get_component_media_cache() -> BaseCache:
|
|||
component_media_cache = LocMemCache(
|
||||
"django-components-media",
|
||||
{
|
||||
"TIMEOUT": None, # No timeout
|
||||
"MAX_ENTRIES": None, # No max size
|
||||
"CULL_FREQUENCY": 3,
|
||||
# No max size nor timeout
|
||||
# NOTE: Implementation of `BaseCache` coerces the `MAX_ENTRIES` value
|
||||
# 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,
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
|
|
|
@ -19,8 +19,11 @@ from django_components.util.command import ComponentCommand
|
|||
def _gen_subcommands() -> List[Type[ComponentCommand]]:
|
||||
commands: List[Type[ComponentCommand]] = []
|
||||
for extension in extensions.extensions:
|
||||
if not extension.commands:
|
||||
continue
|
||||
|
||||
ExtCommand = type(
|
||||
"ExtCommand",
|
||||
"ExtRunSubcommand_" + extension.name,
|
||||
(ComponentCommand,),
|
||||
{
|
||||
"name": extension.name,
|
||||
|
|
|
@ -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 registry as registry_
|
||||
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 (
|
||||
DependenciesStrategy,
|
||||
cache_component_css,
|
||||
|
@ -2277,7 +2277,7 @@ class Component(metaclass=ComponentMeta):
|
|||
|
||||
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.registered_name: Optional[str] = registered_name
|
||||
self.args = default(args, [])
|
||||
|
@ -2314,9 +2314,6 @@ class Component(metaclass=ComponentMeta):
|
|||
cls.class_id = hash_comp_cls(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]
|
||||
extensions._init_component_class(cls)
|
||||
extensions.on_component_class_created(OnComponentClassCreatedContext(cls))
|
||||
|
@ -3489,11 +3486,12 @@ class Component(metaclass=ComponentMeta):
|
|||
)
|
||||
)
|
||||
|
||||
# Process Component's JS and CSS
|
||||
cache_component_js(comp_cls)
|
||||
js_input_hash = cache_component_js_vars(comp_cls, js_data) if js_data else None
|
||||
# Cache component's JS and CSS scripts, in case they have been evicted from the cache.
|
||||
cache_component_js(comp_cls, force=False)
|
||||
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
|
||||
|
||||
#############################################################################
|
||||
|
@ -3517,14 +3515,14 @@ class Component(metaclass=ComponentMeta):
|
|||
# Then we can simply apply `template_data` to the context in the same layer
|
||||
# where we apply `context_processor_data` and `component_vars`.
|
||||
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.
|
||||
# Part of fix for https://github.com/django-components/django-components/issues/508
|
||||
# See django_monkeypatch.py
|
||||
if template is not None:
|
||||
template._djc_is_component_nested = bool(
|
||||
context.render_context.get(BLOCK_CONTEXT_KEY) # type: ignore[union-attr]
|
||||
)
|
||||
comp_is_nested = bool(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)
|
||||
component_ctx.template_name = template.name if template else None
|
||||
|
@ -3535,6 +3533,7 @@ class Component(metaclass=ComponentMeta):
|
|||
**component.context_processors_data,
|
||||
# Private context fields
|
||||
_COMPONENT_CONTEXT_KEY: render_id,
|
||||
COMPONENT_IS_NESTED_KEY: comp_is_nested,
|
||||
# NOTE: Public API for variables accessible from within a component's template
|
||||
# See https://github.com/django-components/django-components/issues/280#issuecomment-2081180940
|
||||
"component_vars": ComponentVars(
|
||||
|
@ -3672,7 +3671,7 @@ class Component(metaclass=ComponentMeta):
|
|||
# ```
|
||||
def _gen_component_renderer(
|
||||
self,
|
||||
template: Template,
|
||||
template: Optional[Template],
|
||||
context: Context,
|
||||
component_path: List[str],
|
||||
css_input_hash: Optional[str],
|
||||
|
@ -3697,7 +3696,8 @@ class Component(metaclass=ComponentMeta):
|
|||
component.on_render_before(context, template)
|
||||
|
||||
# Emit signal that the template is about to be rendered
|
||||
template_rendered.send(sender=template, template=template, context=context)
|
||||
if template is not None:
|
||||
template_rendered.send(sender=template, template=template, context=context)
|
||||
|
||||
# Get the component's HTML
|
||||
# To access the *final* output (with all its children rendered) from within `Component.on_render()`,
|
||||
|
|
|
@ -27,10 +27,12 @@ from weakref import WeakKeyDictionary
|
|||
from django.contrib.staticfiles import finders
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
from django.forms.widgets import Media as MediaCls
|
||||
from django.template import Template
|
||||
from django.utils.safestring import SafeData
|
||||
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.logger import logger
|
||||
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
|
||||
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.
|
||||
|
@ -267,6 +269,8 @@ class ComponentMedia:
|
|||
js_file: Union[str, Unset, None] = UNSET
|
||||
css: 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:
|
||||
for inlined_attr in ("template", "js", "css"):
|
||||
|
@ -299,6 +303,7 @@ class ComponentMedia:
|
|||
def reset(self) -> None:
|
||||
self.__dict__.update(self._original.__dict__)
|
||||
self.resolved = False
|
||||
self.resolved_relative_files = False
|
||||
|
||||
|
||||
# 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:
|
||||
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
|
||||
# NOTE: If the `Component.Media` class is explicitly set to `None`, then we should not inherit
|
||||
# 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
|
||||
# will be set to the content of the 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_media,
|
||||
inlined_attr="template",
|
||||
file_attr="template_file",
|
||||
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:
|
||||
|
@ -973,11 +1001,10 @@ def _find_component_dir_containing_file(
|
|||
def _get_asset(
|
||||
comp_cls: Type["Component"],
|
||||
comp_media: ComponentMedia,
|
||||
inlined_attr: str,
|
||||
file_attr: str,
|
||||
inlined_attr: Literal["template", "js", "css"],
|
||||
file_attr: Literal["template_file", "js_file", "css_file"],
|
||||
comp_dirs: List[Path],
|
||||
type: Literal["template", "static"],
|
||||
) -> Union[str, Unset, None]:
|
||||
) -> Tuple[Union[str, Unset, None], Union[Template, Unset, None]]: # Tuple of (content, Template)
|
||||
"""
|
||||
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
|
||||
# ```
|
||||
if asset_content is UNSET and asset_file is UNSET:
|
||||
return UNSET
|
||||
return UNSET, UNSET
|
||||
|
||||
# Either file or content attr was set to `None`
|
||||
# ```py
|
||||
|
@ -1031,7 +1058,7 @@ def _get_asset(
|
|||
if (asset_content in (UNSET, None) and asset_file is None) or (
|
||||
asset_content is None and asset_file in (UNSET, None)
|
||||
):
|
||||
return None
|
||||
return None, None
|
||||
|
||||
# Received both inlined content and file name
|
||||
# ```py
|
||||
|
@ -1061,42 +1088,68 @@ def _get_asset(
|
|||
# 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 = "..."`)
|
||||
# then there's nothing to resolve. Return as is.
|
||||
if asset_content is not UNSET:
|
||||
return asset_content
|
||||
# then there's nothing to resolve. Use it as is.
|
||||
if not isinstance(asset_content, Unset):
|
||||
if asset_content is None:
|
||||
return None, None
|
||||
|
||||
if asset_file is None:
|
||||
return None
|
||||
content: str = asset_content
|
||||
|
||||
# The rest of the code assumes that we were given only a file name
|
||||
asset_file = cast(str, asset_file)
|
||||
# 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
|
||||
|
||||
if type == "template":
|
||||
# NOTE: While we return on the "source" (plain string) of the template,
|
||||
# by calling `load_component_template()`, we also cache the Template instance.
|
||||
# So later in Component's `render_impl()`, we don't have to re-compile the Template.
|
||||
template = load_component_template(comp_cls, asset_file)
|
||||
return template.source
|
||||
# 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:
|
||||
return None, None
|
||||
|
||||
# For static files, we have a few options:
|
||||
# 1. Check if the file is in one of the components' directories
|
||||
full_path = resolve_file(asset_file, comp_dirs)
|
||||
asset_file = cast(str, asset_file)
|
||||
|
||||
# 2. If not, check if it's in the static files
|
||||
if full_path is None:
|
||||
full_path = finders.find(asset_file)
|
||||
if inlined_attr == "template":
|
||||
# NOTE: `load_component_template()` applies `on_template_loaded()` and `on_template_compiled()` hooks.
|
||||
template = load_component_template(comp_cls, filepath=asset_file, content=None)
|
||||
return template.source, template
|
||||
|
||||
if full_path is None:
|
||||
# NOTE: The short name, e.g. `js` or `css` is used in the error message for convenience
|
||||
raise ValueError(f"Could not find {inlined_attr} file {asset_file}")
|
||||
# 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
|
||||
full_path = resolve_file(asset_file, comp_dirs)
|
||||
|
||||
# NOTE: Use explicit encoding for compat with Windows, see #1074
|
||||
asset_content = Path(full_path).read_text(encoding="utf8")
|
||||
# 2. If not, check if it's in the static files
|
||||
if full_path is None:
|
||||
full_path = finders.find(asset_file)
|
||||
|
||||
# TODO: Apply `extensions.on_js_preprocess()` and `extensions.on_css_preprocess()`
|
||||
# NOTE: `on_template_preprocess()` is already applied inside `load_component_template()`
|
||||
if full_path is None:
|
||||
# NOTE: The short name, e.g. `js` or `css` is used in the error message for convenience
|
||||
raise ValueError(f"Could not find {inlined_attr} file {asset_file}")
|
||||
|
||||
return asset_content
|
||||
# NOTE: Use explicit encoding for compat with Windows, see #1074
|
||||
content = Path(full_path).read_text(encoding="utf8")
|
||||
|
||||
# NOTE: `on_template_loaded()` 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 content, None
|
||||
|
||||
|
||||
def is_set(value: Union[T, Unset, None]) -> TypeGuard[T]:
|
||||
|
|
|
@ -11,6 +11,7 @@ from django.template import Context
|
|||
from django_components.util.misc import get_last_index
|
||||
|
||||
_COMPONENT_CONTEXT_KEY = "_DJC_COMPONENT_CTX"
|
||||
COMPONENT_IS_NESTED_KEY = "_DJC_COMPONENT_IS_NESTED"
|
||||
_STRATEGY_CONTEXT_KEY = "DJC_DEPS_STRATEGY"
|
||||
_INJECT_CONTEXT_KEY_PREFIX = "_DJC_INJECT__"
|
||||
|
||||
|
|
|
@ -107,13 +107,16 @@ def _cache_script(
|
|||
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
|
||||
among all instances of the same component. So even if the component is rendered multiple
|
||||
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
|
||||
|
||||
_cache_script(
|
||||
|
@ -167,13 +170,16 @@ def wrap_component_js(comp_cls: Type["Component"], content: str) -> str:
|
|||
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
|
||||
among all instances of the same component. So even if the component is rendered multiple
|
||||
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
|
||||
|
||||
_cache_script(
|
||||
|
|
|
@ -16,7 +16,7 @@ from typing import (
|
|||
)
|
||||
|
||||
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_components.app_settings import app_settings
|
||||
|
@ -168,6 +168,42 @@ class OnSlotRenderedContext(NamedTuple):
|
|||
"""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
|
||||
################################################
|
||||
|
@ -763,6 +799,108 @@ class ComponentExtension(metaclass=ExtensionMeta):
|
|||
"""
|
||||
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
|
||||
##########################
|
||||
|
@ -909,7 +1047,7 @@ class ExtensionManager:
|
|||
# - `MyExtensionBase` is the base class that the extension class inherits from.
|
||||
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)
|
||||
if extension_defaults:
|
||||
# Create dummy class that holds the extension defaults
|
||||
|
@ -1169,9 +1307,34 @@ class ExtensionManager:
|
|||
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]:
|
||||
for extension in self.extensions:
|
||||
result = extension.on_slot_rendered(ctx)
|
||||
|
|
21
src/django_components/extensions/dependencies.py
Normal file
21
src/django_components/extensions/dependencies.py
Normal 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)
|
|
@ -28,7 +28,7 @@ from django.utils.html import conditional_escape
|
|||
from django.utils.safestring import SafeString, mark_safe
|
||||
|
||||
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.node import BaseNode
|
||||
from django_components.perfutil.component import component_context_cache
|
||||
|
@ -742,7 +742,7 @@ class SlotNode(BaseNode):
|
|||
# 1. Using the "django" context behavior
|
||||
# 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.
|
||||
#
|
||||
# ------------------------------------------------------------------------------------------
|
||||
|
@ -825,7 +825,7 @@ class SlotNode(BaseNode):
|
|||
if parent_index is not None:
|
||||
ctx_id_with_fills = context.dicts[parent_index][_COMPONENT_CONTEXT_KEY]
|
||||
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
|
||||
trace_component_msg(
|
||||
|
@ -1585,8 +1585,6 @@ def _nodelist_to_slot(
|
|||
# and binds the context.
|
||||
template = Template("")
|
||||
template.nodelist = nodelist
|
||||
# This allows the template to access current RenderContext layer.
|
||||
template._djc_is_component_nested = True
|
||||
|
||||
def render_func(ctx: SlotContext) -> SlotResult:
|
||||
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)
|
||||
|
||||
# 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`
|
||||
# so that the dependencies are processed only once the whole component tree is rendered.
|
||||
with context.push({"DJC_DEPS_STRATEGY": "ignore"}):
|
||||
# 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`
|
||||
# so that the dependencies are processed only once the whole component tree is rendered.
|
||||
# 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)
|
||||
|
||||
# After the rendering is done, remove the `extra_context` from the context stack
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import sys
|
||||
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 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_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.logger import trace_component_msg
|
||||
from django_components.util.misc import get_import_path, get_module_info
|
||||
|
@ -98,7 +98,7 @@ def prepare_component_template(
|
|||
yield template
|
||||
return
|
||||
|
||||
if not is_template_cls_patched(template):
|
||||
if not is_cls_patched(template):
|
||||
raise RuntimeError(
|
||||
"Django-components received a Template instance which was not patched."
|
||||
"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"] = []
|
||||
|
||||
|
||||
def load_component_template(component_cls: Type["Component"], filepath: str) -> Template:
|
||||
if component_cls._template is not None:
|
||||
return component_cls._template
|
||||
def load_component_template(
|
||||
component_cls: Type["Component"],
|
||||
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))
|
||||
|
||||
# Use Django's `get_template()` to load the template
|
||||
template = _load_django_template(filepath)
|
||||
if filepath is not None:
|
||||
# Use Django's `get_template()` to load the template file
|
||||
template = _load_django_template(filepath)
|
||||
template = ensure_unique_template(component_cls, template)
|
||||
|
||||
# If template.origin.component_cls is already set, then this
|
||||
# Template instance was cached by Django / template loaders.
|
||||
# In that case we want to make a copy of the template which would
|
||||
# be owned by the current Component class.
|
||||
# Thus each Component has it's own Template instance with their own Origins
|
||||
# pointing to the correct Component class.
|
||||
if get_component_from_origin(template.origin) is not None:
|
||||
elif content is not None:
|
||||
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
|
||||
# be owned by the current Component class.
|
||||
# Thus each Component has it's own Template instance with their own Origins
|
||||
# pointing to the correct Component class.
|
||||
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)
|
||||
set_component_to_origin(origin_copy, component_cls)
|
||||
template = Template(template.source, origin=origin_copy, name=template.name, engine=template.engine)
|
||||
|
||||
component_cls._template = template
|
||||
|
||||
loading_components.pop()
|
||||
|
||||
return template
|
||||
|
||||
|
||||
|
@ -250,24 +265,13 @@ def _get_component_template(component: "Component") -> Optional[Template]:
|
|||
template = None
|
||||
template_string = template_sources["get_template"]
|
||||
elif component.template or component.template_file:
|
||||
# If the template was loaded from `Component.template_file`, then the Template
|
||||
# instance was already created and cached in `Component._template`.
|
||||
# If the template was loaded from `Component.template` or `Component.template_file`,
|
||||
# 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
|
||||
# treat Templates AND their nodelists as IMMUTABLE.
|
||||
if component.__class__._template is not None:
|
||||
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 = component.__class__._component_media._template # type: ignore[attr-defined]
|
||||
template_string = None
|
||||
# No template
|
||||
else:
|
||||
template = None
|
||||
|
@ -278,7 +282,7 @@ def _get_component_template(component: "Component") -> Optional[Template]:
|
|||
return template
|
||||
# Create the template from the string
|
||||
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
|
||||
# via `Component.on_render()`
|
||||
|
@ -286,7 +290,7 @@ def _get_component_template(component: "Component") -> Optional[Template]:
|
|||
|
||||
|
||||
def _create_template_from_string(
|
||||
component: "Component",
|
||||
component: Type["Component"],
|
||||
template_string: str,
|
||||
is_component_template: bool = False,
|
||||
) -> 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
|
||||
_, _, module_filepath = get_module_info(component.__class__)
|
||||
_, _, module_filepath = get_module_info(component)
|
||||
origin = Origin(
|
||||
name=f"{module_filepath}::{component.__class__.__name__}",
|
||||
name=f"{module_filepath}::{component.__name__}",
|
||||
template_name=None,
|
||||
loader=None,
|
||||
)
|
||||
|
||||
set_component_to_origin(origin, component.__class__)
|
||||
set_component_to_origin(origin, component)
|
||||
|
||||
if is_component_template:
|
||||
template = Template(template_string, name=origin.template_name, origin=origin)
|
||||
component.__class__._template = template
|
||||
else:
|
||||
# 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
|
||||
|
|
|
@ -1,23 +1,28 @@
|
|||
from typing import Any, Optional, Type
|
||||
|
||||
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.extension import OnTemplateCompiledContext, OnTemplateLoadedContext, extensions
|
||||
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.
|
||||
def monkeypatch_template_cls(template_cls: Type[Template]) -> None:
|
||||
if is_cls_patched(template_cls):
|
||||
return
|
||||
|
||||
monkeypatch_template_init(template_cls)
|
||||
monkeypatch_template_compile_nodelist(template_cls)
|
||||
monkeypatch_template_render(template_cls)
|
||||
template_cls._djc_patched = True
|
||||
|
||||
|
||||
# Patch `Template.__init__` to apply `extensions.on_template_preprocess()` if the template
|
||||
# belongs to a Component.
|
||||
# Patch `Template.__init__` to apply `on_template_loaded()` and `on_template_compiled()`
|
||||
# extension hooks if the template belongs to a Component.
|
||||
def monkeypatch_template_init(template_cls: Type[Template]) -> None:
|
||||
original_init = template_cls.__init__
|
||||
|
||||
|
@ -27,6 +32,7 @@ def monkeypatch_template_init(template_cls: Type[Template]) -> None:
|
|||
self: Template,
|
||||
template_string: Any,
|
||||
origin: Optional[Origin] = None,
|
||||
name: Optional[str] = None,
|
||||
*args: Any,
|
||||
**kwargs: Any,
|
||||
) -> None:
|
||||
|
@ -56,11 +62,27 @@ def monkeypatch_template_init(template_cls: Type[Template]) -> None:
|
|||
component_cls = None
|
||||
|
||||
if component_cls is not None:
|
||||
# TODO - Apply extensions.on_template_preprocess() here.
|
||||
# Then also test both cases when template as `template` or `template_file`.
|
||||
pass
|
||||
template_string = str(template_string)
|
||||
template_string = extensions.on_template_loaded(
|
||||
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__
|
||||
|
||||
|
@ -110,7 +132,7 @@ def monkeypatch_template_compile_nodelist(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`
|
||||
# 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
|
||||
#
|
||||
|
@ -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,
|
||||
# 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
|
||||
# did NOT do that, so the monkey-patched method is more robust, and can be e.g. copied
|
||||
# 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
|
||||
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:
|
||||
"Display stage -- can be called many times"
|
||||
# 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
|
||||
else:
|
||||
# MUST be `True` for templates that are NOT import with `{% extends %}` tag,
|
||||
# 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`,
|
||||
# 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
|
||||
|
||||
|
||||
def is_template_cls_patched(template_cls: Type[Template]) -> bool:
|
||||
return getattr(template_cls, "_djc_patched", False)
|
||||
def monkeypatch_include_node(include_node_cls: Type[Node]) -> None:
|
||||
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)
|
||||
|
|
|
@ -25,7 +25,7 @@ def component_error_message(component_path: List[str]) -> Generator[None, None,
|
|||
if not components:
|
||||
orig_msg = str(err.args[0])
|
||||
else:
|
||||
orig_msg = err.args[0].split("\n", 1)[-1]
|
||||
orig_msg = str(err.args[0]).split("\n", 1)[-1]
|
||||
else:
|
||||
orig_msg = str(err)
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
{% include "test_cached_component_inside_include_sub.html" %}
|
||||
{% include "component_inside_include_sub.html" %}
|
||||
{% block content %}
|
||||
THIS_IS_IN_BASE_TEMPLATE_SO_SHOULD_BE_OVERRIDDEN
|
||||
{% endblock %}
|
|
@ -50,8 +50,6 @@ class TestImportLibraries:
|
|||
}
|
||||
)
|
||||
def test_import_libraries(self):
|
||||
# Ensure we start with a clean state
|
||||
registry.clear()
|
||||
all_components = registry.all().copy()
|
||||
assert "single_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):
|
||||
# Ensure we start with a clean state
|
||||
registry.clear()
|
||||
all_components = registry.all().copy()
|
||||
assert "single_file_component" not in all_components
|
||||
assert "multi_file_component" not in all_components
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
from django.core.cache.backends.locmem import LocMemCache
|
||||
|
||||
from django_components import Component, register
|
||||
from django_components.testing import djc_test
|
||||
from django_components.util.cache import LRUCache
|
||||
|
@ -68,16 +66,26 @@ class TestCache:
|
|||
|
||||
@djc_test
|
||||
class TestComponentMediaCache:
|
||||
@djc_test(components_settings={"cache": "test-cache"})
|
||||
def test_component_media_caching(self):
|
||||
test_cache = LocMemCache(
|
||||
"test-cache",
|
||||
{
|
||||
"TIMEOUT": None, # No timeout
|
||||
"MAX_ENTRIES": None, # No max size
|
||||
"CULL_FREQUENCY": 3,
|
||||
@djc_test(
|
||||
components_settings={"cache": "test-cache"},
|
||||
django_settings={
|
||||
"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
|
||||
"OPTIONS": {
|
||||
"MAX_ENTRIES": 10_000,
|
||||
},
|
||||
},
|
||||
},
|
||||
)
|
||||
},
|
||||
)
|
||||
def test_component_media_caching(self):
|
||||
from django.core.cache import caches
|
||||
|
||||
test_cache = caches["test-cache"]
|
||||
|
||||
@register("test_simple")
|
||||
class TestSimpleComponent(Component):
|
||||
|
@ -108,14 +116,6 @@ class TestComponentMediaCache:
|
|||
def get_css_data(self, args, kwargs, slots, context):
|
||||
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
|
||||
assert test_cache.has_key(f"__components:{TestMediaAndVarsComponent.class_id}:js")
|
||||
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}: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`
|
||||
# NOTE: The hashes is generated from the data.
|
||||
js_vars_hash = "216ecc"
|
||||
|
|
|
@ -103,6 +103,7 @@ class TestExtensionsListCommand:
|
|||
"===============\n"
|
||||
"cache \n"
|
||||
"defaults \n"
|
||||
"dependencies \n"
|
||||
"view \n"
|
||||
"debug_highlight"
|
||||
)
|
||||
|
@ -121,6 +122,7 @@ class TestExtensionsListCommand:
|
|||
"===============\n"
|
||||
"cache \n"
|
||||
"defaults \n"
|
||||
"dependencies \n"
|
||||
"view \n"
|
||||
"debug_highlight\n"
|
||||
"empty \n"
|
||||
|
@ -141,6 +143,7 @@ class TestExtensionsListCommand:
|
|||
"===============\n"
|
||||
"cache \n"
|
||||
"defaults \n"
|
||||
"dependencies \n"
|
||||
"view \n"
|
||||
"debug_highlight\n"
|
||||
"empty \n"
|
||||
|
@ -161,6 +164,7 @@ class TestExtensionsListCommand:
|
|||
"===============\n"
|
||||
"cache \n"
|
||||
"defaults \n"
|
||||
"dependencies \n"
|
||||
"view \n"
|
||||
"debug_highlight\n"
|
||||
"empty \n"
|
||||
|
@ -179,6 +183,7 @@ class TestExtensionsListCommand:
|
|||
assert output.strip() == (
|
||||
"cache \n"
|
||||
"defaults \n"
|
||||
"dependencies \n"
|
||||
"view \n"
|
||||
"debug_highlight\n"
|
||||
"empty \n"
|
||||
|
@ -206,21 +211,16 @@ class TestExtensionsRunCommand:
|
|||
output
|
||||
== dedent(
|
||||
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.
|
||||
|
||||
{OPTIONS_TITLE}:
|
||||
-h, --help show this help message and exit
|
||||
-h, --help show this help message and exit
|
||||
|
||||
subcommands:
|
||||
{{cache,defaults,view,debug_highlight,empty,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}}
|
||||
dummy Run commands added by the 'dummy' extension.
|
||||
"""
|
||||
).lstrip()
|
||||
)
|
||||
|
@ -231,19 +231,23 @@ class TestExtensionsRunCommand:
|
|||
def test_run_command_ext_empty(self):
|
||||
out = StringIO()
|
||||
with patch("sys.stdout", new=out):
|
||||
call_command("components", "ext", "run", "empty")
|
||||
call_command("components", "ext", "run", "dummy")
|
||||
output = out.getvalue()
|
||||
|
||||
assert (
|
||||
output
|
||||
== dedent(
|
||||
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}:
|
||||
-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()
|
||||
)
|
||||
|
|
|
@ -219,7 +219,7 @@ class TestComponentCache:
|
|||
|
||||
template = Template(
|
||||
"""
|
||||
{% extends "test_cached_component_inside_include_base.html" %}
|
||||
{% extends "component_inside_include_base.html" %}
|
||||
{% block content %}
|
||||
THIS_IS_IN_ACTUAL_TEMPLATE_SO_SHOULD_NOT_BE_OVERRIDDEN
|
||||
{% endblock %}
|
||||
|
|
|
@ -15,7 +15,6 @@ from django.utils.safestring import mark_safe
|
|||
from pytest_django.asserts import assertHTMLEqual, assertInHTML
|
||||
|
||||
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 .testutils import setup_test_config
|
||||
|
@ -46,7 +45,7 @@ class TestMainMedia:
|
|||
rendered = TestComponent.render()
|
||||
|
||||
assertInHTML(
|
||||
'<div class="html-css-only" data-djc-id-ca1bc3e>Content</div>',
|
||||
'<div class="html-css-only" data-djc-id-ca1bc40>Content</div>',
|
||||
rendered,
|
||||
)
|
||||
assertInHTML(
|
||||
|
@ -70,6 +69,9 @@ class TestMainMedia:
|
|||
assert TestComponent.css == ".html-css-only { color: blue; }"
|
||||
assert TestComponent.js == "console.log('HTML and JS only');"
|
||||
|
||||
assert isinstance(TestComponent._template, Template)
|
||||
assert TestComponent._template.origin.component_cls is TestComponent
|
||||
|
||||
@djc_test(
|
||||
django_settings={
|
||||
"STATICFILES_DIRS": [
|
||||
|
@ -127,6 +129,9 @@ class TestMainMedia:
|
|||
assert TestComponent.css == ".html-css-only {\n color: blue;\n}\n"
|
||||
assert TestComponent.js == 'console.log("JS file");\n'
|
||||
|
||||
assert isinstance(TestComponent._template, Template)
|
||||
assert TestComponent._template.origin.component_cls is TestComponent
|
||||
|
||||
@djc_test(
|
||||
django_settings={
|
||||
"STATICFILES_DIRS": [
|
||||
|
@ -151,6 +156,9 @@ class TestMainMedia:
|
|||
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 isinstance(TestComponent._template, Template)
|
||||
assert TestComponent._template.origin.component_cls is TestComponent
|
||||
|
||||
rendered = Template(
|
||||
"""
|
||||
{% load component_tags %}
|
||||
|
@ -195,13 +203,18 @@ class TestMainMedia:
|
|||
class TestComponent(AppLvlCompComponent):
|
||||
pass
|
||||
|
||||
# NOTE: Since this is a subclass, actual CSS is defined on the parent class, and thus
|
||||
# the corresponding ComponentMedia instance is also on the parent class.
|
||||
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]
|
||||
|
||||
# Access the property to load the CSS
|
||||
_ = TestComponent.css
|
||||
# NOTE: Currently the components' JS/CSS are loaded eagerly, to make the JS/CSS
|
||||
# files available via endpoints. If that is no longer true, uncomment the
|
||||
# following lines to test the lazy loading of the CSS.
|
||||
#
|
||||
# # Since this is a subclass, actual CSS is defined on the parent class, and thus
|
||||
# # the corresponding ComponentMedia instance is also on the parent class.
|
||||
# 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_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_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):
|
||||
class FilteredComponent(Component):
|
||||
template: types.django_html = """
|
||||
|
@ -1037,73 +1053,121 @@ class TestSubclassingAttributes:
|
|||
class TestComp(Component):
|
||||
js = None
|
||||
js_file = None
|
||||
template = None
|
||||
template_file = None
|
||||
|
||||
assert TestComp.js 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):
|
||||
with pytest.raises(
|
||||
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):
|
||||
js = "console.log('hi')"
|
||||
js_file = None
|
||||
template = "<h1>hi</h1>"
|
||||
template_file = None
|
||||
|
||||
def test_both_non_none_raises(self):
|
||||
with pytest.raises(
|
||||
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):
|
||||
js = "console.log('hi')"
|
||||
js_file = "file.js"
|
||||
template = "<h1>hi</h1>"
|
||||
template_file = "file.html"
|
||||
|
||||
def test_parent_non_null_child_non_null(self):
|
||||
class ParentComp(Component):
|
||||
js = "console.log('parent')"
|
||||
template = "<h1>parent</h1>"
|
||||
|
||||
class TestComp(ParentComp):
|
||||
js = "console.log('child')"
|
||||
template = "<h1>child</h1>"
|
||||
|
||||
assert TestComp.js == "console.log('child')"
|
||||
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):
|
||||
class ParentComp(Component):
|
||||
js = None
|
||||
template = None
|
||||
|
||||
class TestComp(ParentComp):
|
||||
js = "console.log('child')"
|
||||
template = "<h1>child</h1>"
|
||||
|
||||
assert TestComp.js == "console.log('child')"
|
||||
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):
|
||||
class ParentComp(Component):
|
||||
js: Optional[str] = "console.log('parent')"
|
||||
template: Optional[str] = "<h1>parent</h1>"
|
||||
|
||||
class TestComp(ParentComp):
|
||||
js = None
|
||||
template = None
|
||||
|
||||
assert TestComp.js 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):
|
||||
class ParentComp(Component):
|
||||
js = None
|
||||
template = None
|
||||
|
||||
class TestComp(ParentComp):
|
||||
js = None
|
||||
template = None
|
||||
|
||||
assert TestComp.js 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):
|
||||
class GrandParentComp(Component):
|
||||
js = "console.log('grandparent')"
|
||||
template = "<h1>grandparent</h1>"
|
||||
|
||||
class ParentComp(GrandParentComp):
|
||||
pass
|
||||
|
@ -1113,45 +1177,97 @@ class TestSubclassingAttributes:
|
|||
|
||||
assert TestComp.js == "console.log('grandparent')"
|
||||
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):
|
||||
class GrandParentComp(Component):
|
||||
js: Optional[str] = "console.log('grandparent')"
|
||||
template: Optional[str] = "<h1>grandparent</h1>"
|
||||
|
||||
class ParentComp(GrandParentComp):
|
||||
js = None
|
||||
template = None
|
||||
|
||||
class TestComp(ParentComp):
|
||||
pass
|
||||
|
||||
assert TestComp.js 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):
|
||||
class GrandParentComp(Component):
|
||||
js = "console.log('grandparent')"
|
||||
template = "<h1>grandparent</h1>"
|
||||
|
||||
class ParentComp(GrandParentComp):
|
||||
pass
|
||||
|
||||
class TestComp(ParentComp):
|
||||
js = "console.log('child')"
|
||||
template = "<h1>child</h1>"
|
||||
|
||||
assert TestComp.js == "console.log('child')"
|
||||
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):
|
||||
class GrandParentComp(Component):
|
||||
js = None
|
||||
template = None
|
||||
|
||||
class ParentComp(GrandParentComp):
|
||||
pass
|
||||
|
||||
class TestComp(ParentComp):
|
||||
js = "console.log('child')"
|
||||
template = "<h1>child</h1>"
|
||||
|
||||
assert TestComp.js == "console.log('child')"
|
||||
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
|
||||
|
|
|
@ -3,7 +3,7 @@ from typing import Any, Callable, Dict, List, cast
|
|||
|
||||
import pytest
|
||||
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_components import Component, Slot, SlotNode, register, registry
|
||||
|
@ -23,10 +23,15 @@ from django_components.extension import (
|
|||
OnComponentDataContext,
|
||||
OnComponentRenderedContext,
|
||||
OnSlotRenderedContext,
|
||||
OnTemplateLoadedContext,
|
||||
OnTemplateCompiledContext,
|
||||
OnJsLoadedContext,
|
||||
OnCssLoadedContext,
|
||||
)
|
||||
from django_components.extensions.cache import CacheExtension
|
||||
from django_components.extensions.debug_highlight import DebugHighlightExtension
|
||||
from django_components.extensions.defaults import DefaultsExtension
|
||||
from django_components.extensions.dependencies import DependenciesExtension
|
||||
from django_components.extensions.view import ViewExtension
|
||||
|
||||
from django_components.testing import djc_test
|
||||
|
@ -85,6 +90,10 @@ class DummyExtension(ComponentExtension):
|
|||
"on_component_data": [],
|
||||
"on_component_rendered": [],
|
||||
"on_slot_rendered": [],
|
||||
"on_template_loaded": [],
|
||||
"on_template_compiled": [],
|
||||
"on_js_loaded": [],
|
||||
"on_css_loaded": [],
|
||||
}
|
||||
|
||||
urls = [
|
||||
|
@ -126,6 +135,18 @@ class DummyExtension(ComponentExtension):
|
|||
def on_slot_rendered(self, ctx: OnSlotRenderedContext) -> None:
|
||||
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):
|
||||
name = "test_nested_extension"
|
||||
|
@ -182,16 +203,30 @@ def with_registry(on_created: Callable):
|
|||
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
|
||||
class TestExtension:
|
||||
@djc_test(components_settings={"extensions": [DummyExtension]})
|
||||
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[1], DefaultsExtension)
|
||||
assert isinstance(app_settings.EXTENSIONS[2], ViewExtension)
|
||||
assert isinstance(app_settings.EXTENSIONS[3], DebugHighlightExtension)
|
||||
assert isinstance(app_settings.EXTENSIONS[4], DummyExtension)
|
||||
assert isinstance(app_settings.EXTENSIONS[2], DependenciesExtension)
|
||||
assert isinstance(app_settings.EXTENSIONS[3], ViewExtension)
|
||||
assert isinstance(app_settings.EXTENSIONS[4], DebugHighlightExtension)
|
||||
assert isinstance(app_settings.EXTENSIONS[5], DummyExtension)
|
||||
|
||||
@djc_test(components_settings={"extensions": [DummyExtension]})
|
||||
def test_access_component_from_extension(self):
|
||||
|
@ -230,7 +265,7 @@ class TestExtension:
|
|||
class TestExtensionHooks:
|
||||
@djc_test(components_settings={"extensions": [DummyExtension]})
|
||||
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_deleted"]) == 0
|
||||
|
@ -262,7 +297,7 @@ class TestExtensionHooks:
|
|||
|
||||
@djc_test(components_settings={"extensions": [DummyExtension]})
|
||||
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_deleted"]) == 0
|
||||
|
@ -299,7 +334,7 @@ class TestExtensionHooks:
|
|||
return {"name": kwargs.get("name", "World")}
|
||||
|
||||
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
|
||||
assert len(extension.calls["on_component_registered"]) == 1
|
||||
|
@ -337,7 +372,7 @@ class TestExtensionHooks:
|
|||
test_slots = {"content": "Some content"}
|
||||
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
|
||||
assert len(extension.calls["on_component_input"]) == 1
|
||||
|
@ -386,7 +421,7 @@ class TestExtensionHooks:
|
|||
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
|
||||
assert len(extension.calls["on_component_rendered"]) == 1
|
||||
|
@ -415,7 +450,7 @@ class TestExtensionHooks:
|
|||
|
||||
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
|
||||
assert len(extension.calls["on_slot_rendered"]) == 1
|
||||
|
@ -469,6 +504,120 @@ class TestExtensionHooks:
|
|||
rendered = TestComponent.render(args=(), kwargs={"name": "Test"})
|
||||
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
|
||||
class TestExtensionViews:
|
||||
|
|
|
@ -96,3 +96,19 @@ class TestTemplateSignal:
|
|||
templates_used = _get_templates_used_to_render(template)
|
||||
assert "slotted_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"]
|
||||
|
|
|
@ -25,6 +25,17 @@ def gen_blocked_and_slotted_component():
|
|||
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
|
||||
#######################
|
||||
|
@ -536,6 +547,112 @@ class TestExtendsCompat:
|
|||
"""
|
||||
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)
|
||||
def test_component_inside_block(self, components_settings):
|
||||
registry.register("slotted_component", gen_slotted_component())
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue