mirror of
https://github.com/django-components/django-components.git
synced 2025-09-20 12:49:45 +00:00
refactor: remove middleware, add strategy "raw", and call render_deps() from within Template.render() (#1166)
* refactor: remove middleware, add strategy "raw", and call render_deps() from within Template.render() * refactor: fix formatting * refactor: fix benchmark tests * refactor: avoid processing deps if rendered HTML contains no components * refactor: remove comments * refactor: rename "raw" to "ignore"
This commit is contained in:
parent
1049c08324
commit
6253042e9e
37 changed files with 771 additions and 657 deletions
66
CHANGELOG.md
66
CHANGELOG.md
|
@ -26,6 +26,44 @@
|
||||||
|
|
||||||
See [Migrating from generics to class attributes](https://django-components.github.io/django-components/latest/concepts/advanced/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/latest/concepts/advanced/typing_and_validation/#migrating-from-generics-to-class-attributes) for more info.
|
||||||
|
|
||||||
|
- The middleware `ComponentDependencyMiddleware` was removed as it is no longer needed.
|
||||||
|
|
||||||
|
The middleware served one purpose - to render the JS and CSS dependencies of components
|
||||||
|
when you rendered templates with `Template.render()` or `django.shortcuts.render()` and those templates contained `{% component %}` tags.
|
||||||
|
|
||||||
|
Now, the JS and CSS dependencies of components are automatically rendered,
|
||||||
|
even when you render Templates with `Template.render()` or `django.shortcuts.render()`.
|
||||||
|
|
||||||
|
- NOTE: If you rendered HTML with `Component.render()` or `Component.render_to_response()`, the JS and CSS were already rendered.
|
||||||
|
|
||||||
|
To disable this behavior, set the `DJC_DEPS_STRATEGY` context key to `"ignore"`
|
||||||
|
when rendering the template:
|
||||||
|
|
||||||
|
```py
|
||||||
|
# With `Template.render()`:
|
||||||
|
template = Template(template_str)
|
||||||
|
rendered = template.render(Context({"DJC_DEPS_STRATEGY": "ignore"}))
|
||||||
|
|
||||||
|
# Or with django.shortcuts.render():
|
||||||
|
from django.shortcuts import render
|
||||||
|
rendered = render(
|
||||||
|
request,
|
||||||
|
"my_template.html",
|
||||||
|
context={"DJC_DEPS_STRATEGY": "ignore"},
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
In fact, you can set the `DJC_DEPS_STRATEGY` context key to any of the strategies:
|
||||||
|
|
||||||
|
- `"document"`
|
||||||
|
- `"fragment"`
|
||||||
|
- `"simple"`
|
||||||
|
- `"prepend"`
|
||||||
|
- `"append"`
|
||||||
|
- `"ignore"`
|
||||||
|
|
||||||
|
See [Dependencies rendering](https://django-components.github.io/django-components/0.140/concepts/advanced/rendering_js_css/) for more info.
|
||||||
|
|
||||||
- The interface of the not-yet-released `get_js_data()` and `get_css_data()` methods has changed to
|
- The interface of the not-yet-released `get_js_data()` and `get_css_data()` methods has changed to
|
||||||
match `get_template_data()`.
|
match `get_template_data()`.
|
||||||
|
|
||||||
|
@ -192,6 +230,20 @@
|
||||||
Calendar.render_to_response(deps_strategy="fragment")
|
Calendar.render_to_response(deps_strategy="fragment")
|
||||||
```
|
```
|
||||||
|
|
||||||
|
- The `render_dependencies` kwarg in `Component.render()` and `Component.render_to_response()` is now deprecated. Use `deps_strategy="ignore"` instead. The `render_dependencies` kwarg will be removed in v1.
|
||||||
|
|
||||||
|
Before:
|
||||||
|
|
||||||
|
```py
|
||||||
|
Calendar.render_to_response(render_dependencies=False)
|
||||||
|
```
|
||||||
|
|
||||||
|
After:
|
||||||
|
|
||||||
|
```py
|
||||||
|
Calendar.render_to_response(deps_strategy="ignore")
|
||||||
|
```
|
||||||
|
|
||||||
- `SlotContent` was renamed to `SlotInput`. The old name is deprecated and will be removed in v1.
|
- `SlotContent` was renamed to `SlotInput`. The old name is deprecated and will be removed in v1.
|
||||||
|
|
||||||
- In the `on_component_data()` extension hook, the `context_data` field of the context object was superseded by `template_data`.
|
- In the `on_component_data()` extension hook, the `context_data` field of the context object was superseded by `template_data`.
|
||||||
|
@ -258,10 +310,15 @@
|
||||||
|
|
||||||
Read more on [Typing and validation](https://django-components.github.io/django-components/latest/concepts/advanced/typing_and_validation/)
|
Read more on [Typing and validation](https://django-components.github.io/django-components/latest/concepts/advanced/typing_and_validation/)
|
||||||
|
|
||||||
- Render emails or other non-browser HTML with new "dependencies render strategies"
|
- Render emails or other non-browser HTML with new "dependencies strategies"
|
||||||
|
|
||||||
When rendering a component with `Component.render()` or `Component.render_to_response()`,
|
When rendering a component with `Component.render()` or `Component.render_to_response()`,
|
||||||
the `deps_strategy` kwarg (previously `type`) now accepts a new options `"simple"`, `"prepend"`, or `"append"`.
|
the `deps_strategy` kwarg (previously `type`) now accepts additional options:
|
||||||
|
|
||||||
|
- `"simple"`
|
||||||
|
- `"prepend"`
|
||||||
|
- `"append"`
|
||||||
|
- `"ignore"`
|
||||||
|
|
||||||
```py
|
```py
|
||||||
Calendar.render_to_response(
|
Calendar.render_to_response(
|
||||||
|
@ -294,6 +351,11 @@
|
||||||
- Insert JS / CSS after the rendered HTML.
|
- Insert JS / CSS after the rendered HTML.
|
||||||
- Ignores placeholders and any `<head>` / `<body>` tags.
|
- Ignores placeholders and any `<head>` / `<body>` tags.
|
||||||
- No extra script loaded.
|
- No extra script loaded.
|
||||||
|
- `"ignore"`
|
||||||
|
- 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.
|
||||||
|
|
||||||
- `get_component_url()` now optionally accepts `query` and `fragment` arguments.
|
- `get_component_url()` now optionally accepts `query` and `fragment` arguments.
|
||||||
|
|
||||||
|
|
|
@ -319,7 +319,7 @@ Read more about [HTML attributes](https://django-components.github.io/django-com
|
||||||
|
|
||||||
### HTML fragment support
|
### HTML fragment support
|
||||||
|
|
||||||
`django-components` makes integration with HTMX, AlpineJS or jQuery easy by allowing components to be rendered as HTML fragments:
|
`django-components` makes integration with HTMX, AlpineJS or jQuery easy by allowing components to be rendered as [HTML fragments](https://django-components.github.io/django-components/latest/concepts/advanced/html_fragments/):
|
||||||
|
|
||||||
- Components's JS and CSS files are loaded automatically when the fragment is inserted into the DOM.
|
- Components's JS and CSS files are loaded automatically when the fragment is inserted into the DOM.
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
Django-components provides a seamless integration with HTML fragments with AJAX ([HTML over the wire](https://hotwired.dev/)),
|
Django-components provides a seamless integration with HTML fragments with AJAX ([HTML over the wire](https://hotwired.dev/)),
|
||||||
whether you're using jQuery, HTMX, AlpineJS, or vanilla JavaScript.
|
whether you're using jQuery, HTMX, AlpineJS, vanilla JavaScript, or other.
|
||||||
|
|
||||||
When you define a component that has extra JS or CSS, and you use django-components
|
If the fragment component has any JS or CSS, django-components will:
|
||||||
to render the fragment, django-components will:
|
|
||||||
|
|
||||||
- Automatically load the associated JS and CSS
|
- Automatically load the associated JS and CSS
|
||||||
- Ensure that JS is loaded and executed only once even if the fragment is inserted multiple times
|
- Ensure that JS is loaded and executed only once even if the fragment is inserted multiple times
|
||||||
|
@ -24,9 +23,11 @@ to render the fragment, django-components will:
|
||||||
|
|
||||||
## Document and fragment strategies
|
## Document and fragment strategies
|
||||||
|
|
||||||
Components support different "strategies" for rendering JS and CSS.
|
Components support different ["strategies"](../../advanced/rendering_js_css#dependencies-strategies)
|
||||||
|
for rendering JS and CSS.
|
||||||
|
|
||||||
Two of them are used to enable HTML fragments - "document" and "fragment".
|
Two of them are used to enable HTML fragments - ["document"](../../advanced/rendering_js_css#document)
|
||||||
|
and ["fragment"](../../advanced/rendering_js_css#fragment).
|
||||||
|
|
||||||
What's the difference?
|
What's the difference?
|
||||||
|
|
||||||
|
@ -36,7 +37,7 @@ Document strategy assumes that the rendered components will be embedded into the
|
||||||
of the initial page load. This means that:
|
of the initial page load. This means that:
|
||||||
|
|
||||||
- The JS and CSS is embedded into the HTML as `<script>` and `<style>` tags
|
- The JS and CSS is embedded into the HTML as `<script>` and `<style>` tags
|
||||||
(see [JS and CSS output locations](./rendering_js_css.md#js-and-css-output-locations))
|
(see [Default JS / CSS locations](./rendering_js_css.md#default-js-css-locations))
|
||||||
- Django-components injects a JS script for managing JS and CSS assets
|
- Django-components injects a JS script for managing JS and CSS assets
|
||||||
|
|
||||||
A component is rendered as a "document" when:
|
A component is rendered as a "document" when:
|
||||||
|
@ -103,15 +104,12 @@ Then navigate to these URLs:
|
||||||
|
|
||||||
### 1. Define document HTML
|
### 1. Define document HTML
|
||||||
|
|
||||||
|
This is the HTML into which a fragment will be loaded using HTMX.
|
||||||
|
|
||||||
```djc_py title="[root]/components/demo.py"
|
```djc_py title="[root]/components/demo.py"
|
||||||
from django_components import Component, types
|
from django_components import Component, types
|
||||||
|
|
||||||
# HTML into which a fragment will be loaded using HTMX
|
|
||||||
class MyPage(Component):
|
class MyPage(Component):
|
||||||
Class View:
|
|
||||||
def get(self, request):
|
|
||||||
return self.component.render_to_response(request=request)
|
|
||||||
|
|
||||||
template = """
|
template = """
|
||||||
{% load component_tags %}
|
{% load component_tags %}
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
|
@ -135,20 +133,20 @@ class MyPage(Component):
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
class View:
|
||||||
|
def get(self, request):
|
||||||
|
return self.component.render_to_response(request=request)
|
||||||
```
|
```
|
||||||
|
|
||||||
### 2. Define fragment HTML
|
### 2. Define fragment HTML
|
||||||
|
|
||||||
|
The fragment to be inserted into the document.
|
||||||
|
|
||||||
|
IMPORTANT: Don't forget to set `deps_strategy="fragment"`
|
||||||
|
|
||||||
```djc_py title="[root]/components/demo.py"
|
```djc_py title="[root]/components/demo.py"
|
||||||
class Frag(Component):
|
class Frag(Component):
|
||||||
class View:
|
|
||||||
def get(self, request):
|
|
||||||
return self.component.render_to_response(
|
|
||||||
request=request,
|
|
||||||
# IMPORTANT: Don't forget `deps_strategy="fragment"`
|
|
||||||
deps_strategy="fragment",
|
|
||||||
)
|
|
||||||
|
|
||||||
template = """
|
template = """
|
||||||
<div class="frag">
|
<div class="frag">
|
||||||
123
|
123
|
||||||
|
@ -165,6 +163,14 @@ class Frag(Component):
|
||||||
background: blue;
|
background: blue;
|
||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
class View:
|
||||||
|
def get(self, request):
|
||||||
|
return self.component.render_to_response(
|
||||||
|
request=request,
|
||||||
|
# IMPORTANT: Don't forget `deps_strategy="fragment"`
|
||||||
|
deps_strategy="fragment",
|
||||||
|
)
|
||||||
```
|
```
|
||||||
|
|
||||||
### 3. Create view and URLs
|
### 3. Create view and URLs
|
||||||
|
@ -184,15 +190,12 @@ urlpatterns = [
|
||||||
|
|
||||||
### 1. Define document HTML
|
### 1. Define document HTML
|
||||||
|
|
||||||
|
This is the HTML into which a fragment will be loaded using AlpineJS.
|
||||||
|
|
||||||
```djc_py title="[root]/components/demo.py"
|
```djc_py title="[root]/components/demo.py"
|
||||||
from django_components import Component, types
|
from django_components import Component, types
|
||||||
|
|
||||||
# HTML into which a fragment will be loaded using AlpineJS
|
|
||||||
class MyPage(Component):
|
class MyPage(Component):
|
||||||
class View:
|
|
||||||
def get(self, request):
|
|
||||||
return self.component.render_to_response(request=request)
|
|
||||||
|
|
||||||
template = """
|
template = """
|
||||||
{% load component_tags %}
|
{% load component_tags %}
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
|
@ -222,20 +225,20 @@ class MyPage(Component):
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
class View:
|
||||||
|
def get(self, request):
|
||||||
|
return self.component.render_to_response(request=request)
|
||||||
```
|
```
|
||||||
|
|
||||||
### 2. Define fragment HTML
|
### 2. Define fragment HTML
|
||||||
|
|
||||||
|
The fragment to be inserted into the document.
|
||||||
|
|
||||||
|
IMPORTANT: Don't forget to set `deps_strategy="fragment"`
|
||||||
|
|
||||||
```djc_py title="[root]/components/demo.py"
|
```djc_py title="[root]/components/demo.py"
|
||||||
class Frag(Component):
|
class Frag(Component):
|
||||||
class View:
|
|
||||||
def get(self, request):
|
|
||||||
return self.component.render_to_response(
|
|
||||||
request=request,
|
|
||||||
# IMPORTANT: Don't forget `deps_strategy="fragment"`
|
|
||||||
deps_strategy="fragment",
|
|
||||||
)
|
|
||||||
|
|
||||||
# NOTE: We wrap the actual fragment in a template tag with x-if="false" to prevent it
|
# NOTE: We wrap the actual fragment in a template tag with x-if="false" to prevent it
|
||||||
# from being rendered until we have registered the component with AlpineJS.
|
# from being rendered until we have registered the component with AlpineJS.
|
||||||
template = """
|
template = """
|
||||||
|
@ -265,6 +268,14 @@ class Frag(Component):
|
||||||
background: blue;
|
background: blue;
|
||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
class View:
|
||||||
|
def get(self, request):
|
||||||
|
return self.component.render_to_response(
|
||||||
|
request=request,
|
||||||
|
# IMPORTANT: Don't forget `deps_strategy="fragment"`
|
||||||
|
deps_strategy="fragment",
|
||||||
|
)
|
||||||
```
|
```
|
||||||
|
|
||||||
### 3. Create view and URLs
|
### 3. Create view and URLs
|
||||||
|
@ -284,15 +295,12 @@ urlpatterns = [
|
||||||
|
|
||||||
### 1. Define document HTML
|
### 1. Define document HTML
|
||||||
|
|
||||||
|
This is the HTML into which a fragment will be loaded using vanilla JS.
|
||||||
|
|
||||||
```djc_py title="[root]/components/demo.py"
|
```djc_py title="[root]/components/demo.py"
|
||||||
from django_components import Component, types
|
from django_components import Component, types
|
||||||
|
|
||||||
# HTML into which a fragment will be loaded using JS
|
|
||||||
class MyPage(Component):
|
class MyPage(Component):
|
||||||
class View:
|
|
||||||
def get(self, request):
|
|
||||||
return self.component.render_to_response(request=request)
|
|
||||||
|
|
||||||
template = """
|
template = """
|
||||||
{% load component_tags %}
|
{% load component_tags %}
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
|
@ -321,20 +329,20 @@ class MyPage(Component):
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
class View:
|
||||||
|
def get(self, request):
|
||||||
|
return self.component.render_to_response(request=request)
|
||||||
```
|
```
|
||||||
|
|
||||||
### 2. Define fragment HTML
|
### 2. Define fragment HTML
|
||||||
|
|
||||||
|
The fragment to be inserted into the document.
|
||||||
|
|
||||||
|
IMPORTANT: Don't forget to set `deps_strategy="fragment"`
|
||||||
|
|
||||||
```djc_py title="[root]/components/demo.py"
|
```djc_py title="[root]/components/demo.py"
|
||||||
class Frag(Component):
|
class Frag(Component):
|
||||||
class View:
|
|
||||||
def get(self, request):
|
|
||||||
return self.component.render_to_response(
|
|
||||||
request=request,
|
|
||||||
# IMPORTANT: Don't forget `deps_strategy="fragment"`
|
|
||||||
deps_strategy="fragment",
|
|
||||||
)
|
|
||||||
|
|
||||||
template = """
|
template = """
|
||||||
<div class="frag">
|
<div class="frag">
|
||||||
123
|
123
|
||||||
|
@ -351,6 +359,14 @@ class Frag(Component):
|
||||||
background: blue;
|
background: blue;
|
||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
class View:
|
||||||
|
def get(self, request):
|
||||||
|
return self.component.render_to_response(
|
||||||
|
request=request,
|
||||||
|
# IMPORTANT: Don't forget `deps_strategy="fragment"`
|
||||||
|
deps_strategy="fragment",
|
||||||
|
)
|
||||||
```
|
```
|
||||||
|
|
||||||
### 3. Create view and URLs
|
### 3. Create view and URLs
|
||||||
|
|
|
@ -1,15 +1,20 @@
|
||||||
## JS and CSS output locations
|
## Introduction
|
||||||
|
|
||||||
If:
|
Components consist of 3 parts - HTML, JS and CSS.
|
||||||
|
|
||||||
1. Your components use JS and CSS via any of:
|
Handling of HTML is straightforward - it is rendered as is, and inserted where
|
||||||
- [`Component.css`](#TODO)
|
the [`{% component %}`](../../../reference/template_tags#component) tag is.
|
||||||
- [`Component.js`](#TODO)
|
|
||||||
- [`Component.Media.css`](#TODO)
|
|
||||||
- [`Component.Media.js`](#TODO)
|
|
||||||
2. And you use the [`ComponentDependencyMiddleware`](#TODO) middleware
|
|
||||||
|
|
||||||
Then, by default, the components' JS and CSS will be automatically inserted into the HTML:
|
However, handling of JS and CSS is more complex:
|
||||||
|
|
||||||
|
- JS and CSS is are inserted elsewhere in the HTML. As a best practice, JS is placed in the `<body>` HTML tag, and CSS in the `<head>`.
|
||||||
|
- Multiple components may use the same JS and CSS files. We don't want to load the same files multiple times.
|
||||||
|
- Fetching of JS and CSS may block the page, so the JS / CSS should be embedded in the HTML.
|
||||||
|
- Components inserted as HTML fragments need different handling for JS and CSS.
|
||||||
|
|
||||||
|
## Default JS / CSS locations
|
||||||
|
|
||||||
|
If your components use JS and CSS then, by default, the JS and CSS will be automatically inserted into the HTML:
|
||||||
|
|
||||||
- CSS styles will be inserted at the end of the `<head>`
|
- CSS styles will be inserted at the end of the `<head>`
|
||||||
- JS scripts will be inserted at the end of the `<body>`
|
- JS scripts will be inserted at the end of the `<body>`
|
||||||
|
@ -17,8 +22,8 @@ Then, by default, the components' JS and CSS will be automatically inserted into
|
||||||
If you want to place the dependencies elsewhere in the HTML, you can override
|
If you want to place the dependencies elsewhere in the HTML, you can override
|
||||||
the locations by inserting following Django template tags:
|
the locations by inserting following Django template tags:
|
||||||
|
|
||||||
- [`{% component_js_dependencies %}`](#TODO) - Set new location(s) for JS scripts
|
- [`{% component_js_dependencies %}`](../../../reference/template_tags#component_js_dependencies) - Set new location(s) for JS scripts
|
||||||
- [`{% component_css_dependencies %}`](#TODO) - Set new location(s) for CSS styles
|
- [`{% component_css_dependencies %}`](../../../reference/template_tags#component_css_dependencies) - Set new location(s) for CSS styles
|
||||||
|
|
||||||
So if you have a component with JS and CSS:
|
So if you have a component with JS and CSS:
|
||||||
|
|
||||||
|
@ -31,6 +36,7 @@ class MyButton(Component):
|
||||||
Click me!
|
Click me!
|
||||||
</button>
|
</button>
|
||||||
"""
|
"""
|
||||||
|
|
||||||
js: types.js = """
|
js: types.js = """
|
||||||
for (const btnEl of document.querySelectorAll(".my-button")) {
|
for (const btnEl of document.querySelectorAll(".my-button")) {
|
||||||
btnEl.addEventListener("click", () => {
|
btnEl.addEventListener("click", () => {
|
||||||
|
@ -38,6 +44,7 @@ class MyButton(Component):
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
|
|
||||||
css: types.css """
|
css: types.css """
|
||||||
.my-button {
|
.my-button {
|
||||||
background: green;
|
background: green;
|
||||||
|
@ -49,11 +56,13 @@ class MyButton(Component):
|
||||||
css = ["/extra/style.css"]
|
css = ["/extra/style.css"]
|
||||||
```
|
```
|
||||||
|
|
||||||
Then the JS from `MyButton.js` and `MyButton.Media.js` will be rendered at the default place,
|
Then:
|
||||||
or in [`{% component_js_dependencies %}`](#TODO).
|
|
||||||
|
|
||||||
And the CSS from `MyButton.css` and `MyButton.Media.css` will be rendered at the default place,
|
- JS from `MyButton.js` and `MyButton.Media.js` will be rendered at the default place (`<body>`),
|
||||||
or in [`{% component_css_dependencies %}`](#TODO).
|
or in [`{% component_js_dependencies %}`](../../../reference/template_tags#component_js_dependencies).
|
||||||
|
|
||||||
|
- CSS from `MyButton.css` and `MyButton.Media.css` will be rendered at the default place (`<head>`),
|
||||||
|
or in [`{% component_css_dependencies %}`](../../../reference/template_tags#component_css_dependencies).
|
||||||
|
|
||||||
And if you don't specify `{% component_dependencies %}` tags, it is the equivalent of:
|
And if you don't specify `{% component_dependencies %}` tags, it is the equivalent of:
|
||||||
|
|
||||||
|
@ -74,169 +83,62 @@ And if you don't specify `{% component_dependencies %}` tags, it is the equivale
|
||||||
</html>
|
</html>
|
||||||
```
|
```
|
||||||
|
|
||||||
### Setting up the middleware
|
!!! warning
|
||||||
|
|
||||||
[`ComponentDependencyMiddleware`](#TODO) is a Django [middleware](https://docs.djangoproject.com/en/5.1/topics/http/middleware/)
|
If the rendered HTML does NOT contain neither `{% component_dependencies %}` template tags,
|
||||||
designed to manage and inject CSS / JS dependencies of rendered components dynamically.
|
nor `<head>` and `<body>` HTML tags, then the JS and CSS will NOT be inserted!
|
||||||
It ensures that only the necessary stylesheets and scripts are loaded
|
|
||||||
in your HTML responses, based on the components used in your Django templates.
|
|
||||||
|
|
||||||
To set it up, add the middleware to your [`MIDDLEWARE`](https://docs.djangoproject.com/en/5.1/ref/settings/#std-setting-MIDDLEWARE)
|
To force the JS and CSS to be inserted, use the [`"append"`](#append) or [`"prepend"`](#prepend)
|
||||||
in `settings.py`:
|
strategies.
|
||||||
|
|
||||||
```python
|
|
||||||
MIDDLEWARE = [
|
|
||||||
# ... other middleware classes ...
|
|
||||||
'django_components.middleware.ComponentDependencyMiddleware'
|
|
||||||
# ... other middleware classes ...
|
|
||||||
]
|
|
||||||
```
|
|
||||||
|
|
||||||
### `render_dependencies` and rendering JS / CSS without the middleware
|
|
||||||
|
|
||||||
For most scenarios, using the [`ComponentDependencyMiddleware`](#TODO) middleware will be just fine.
|
|
||||||
|
|
||||||
However, this section is for you if you want to:
|
|
||||||
|
|
||||||
- Render HTML that will NOT be sent as a server response
|
|
||||||
- Insert pre-rendered HTML into another component
|
|
||||||
- Render HTML fragments (partials)
|
|
||||||
|
|
||||||
Every time there is an HTML string that has parts which were rendered using components,
|
|
||||||
and any of those components has JS / CSS, then this HTML string MUST be processed with [`render_dependencies()`](#TODO).
|
|
||||||
|
|
||||||
It is actually [`render_dependencies()`](#TODO) that finds all used components in the HTML string,
|
|
||||||
and inserts the component's JS and CSS into `{% component_dependencies %}` tags, or at the default locations.
|
|
||||||
|
|
||||||
#### Render JS / CSS without the middleware
|
|
||||||
|
|
||||||
The truth is that the [`ComponentDependencyMiddleware`](#TODO) middleware just calls [`render_dependencies()`](#TODO),
|
|
||||||
passing in the HTML content. So if you render a template that contained [`{% component %}`](#TODO) tags,
|
|
||||||
you MUST pass the result through [`render_dependencies()`](#TODO). And the middleware is just one of the options.
|
|
||||||
|
|
||||||
Here is how you can achieve the same, without the middleware, using [`render_dependencies()`](#TODO):
|
|
||||||
|
|
||||||
```python
|
|
||||||
from django.template.base import Template
|
|
||||||
from django.template.context import Context
|
|
||||||
from django_component import render_dependencies
|
|
||||||
|
|
||||||
template = Template("""
|
|
||||||
{% load component_tags %}
|
|
||||||
<!doctype html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<title>MyPage</title>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<main>
|
|
||||||
{% component "my_button" %}
|
|
||||||
Click me!
|
|
||||||
{% endcomponent %}
|
|
||||||
</main>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
""")
|
|
||||||
|
|
||||||
rendered = template.render(Context())
|
|
||||||
rendered = render_dependencies(rendered)
|
|
||||||
```
|
|
||||||
|
|
||||||
Same applies if you render a template using Django's [`django.shortcuts.render`](https://docs.djangoproject.com/en/5.1/topics/http/shortcuts/#render):
|
|
||||||
|
|
||||||
```python
|
|
||||||
from django.shortcuts import render
|
|
||||||
|
|
||||||
def my_view(request):
|
|
||||||
rendered = render(request, "pages/home.html")
|
|
||||||
rendered = render_dependencies(rendered)
|
|
||||||
return rendered
|
|
||||||
```
|
|
||||||
|
|
||||||
Alternatively, when you render HTML with [`Component.render()`](#TODO)
|
|
||||||
or [`Component.render_to_response()`](#TODO),
|
|
||||||
these, by default, call [`render_dependencies()`](#TODO) for you, so you don't have to:
|
|
||||||
|
|
||||||
```python
|
|
||||||
from django_components import Component
|
|
||||||
|
|
||||||
class MyButton(Component):
|
|
||||||
...
|
|
||||||
|
|
||||||
# No need to call `render_dependencies()`
|
|
||||||
rendered = MyButton.render()
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Inserting pre-rendered HTML into another component
|
|
||||||
|
|
||||||
In previous section we've shown that [`render_dependencies()`](#TODO) does NOT need to be called
|
|
||||||
when you render a component via [`Component.render()`](#TODO).
|
|
||||||
|
|
||||||
API of django_components makes it possible to compose components in a "React-like" way,
|
|
||||||
where we pre-render a piece of HTML and then insert it into a larger structure.
|
|
||||||
|
|
||||||
To do this, you must add [`render_dependencies=False`](#TODO) to the nested components:
|
|
||||||
|
|
||||||
```python
|
|
||||||
card_actions = CardActions.render(
|
|
||||||
kwargs={"editable": editable},
|
|
||||||
render_dependencies=False,
|
|
||||||
)
|
|
||||||
|
|
||||||
card = Card.render(
|
|
||||||
slots={"actions": card_actions},
|
|
||||||
render_dependencies=False,
|
|
||||||
)
|
|
||||||
|
|
||||||
page = MyPage.render(
|
|
||||||
slots={"card": card},
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
||||||
Why is `render_dependencies=False` required?
|
|
||||||
|
|
||||||
This is a technical limitation of the current implementation.
|
|
||||||
|
|
||||||
As mentioned earlier, each time we call [`Component.render()`](#TODO),
|
|
||||||
we also call [`render_dependencies()`](#TODO).
|
|
||||||
|
|
||||||
However, there is a problem here - When we call [`render_dependencies()`](#TODO)
|
|
||||||
inside [`CardActions.render()`](#TODO),
|
|
||||||
we extract and REMOVE the info on components' JS and CSS from the HTML. But the template
|
|
||||||
of `CardActions` contains no `{% component_depedencies %}` tags, and nor `<head>` nor `<body>` HTML tags.
|
|
||||||
So the component's JS and CSS will NOT be inserted, and will be lost.
|
|
||||||
|
|
||||||
To work around this, you must set [`render_dependencies=False`](#TODO) when rendering pieces of HTML
|
|
||||||
with [`Component.render()`](#TODO) and inserting them into larger structures.
|
|
||||||
|
|
||||||
#### Summary
|
|
||||||
|
|
||||||
1. Every time you render HTML that contained components, you have to call [`render_dependencies()`](#TODO)
|
|
||||||
on the rendered output.
|
|
||||||
2. There are several ways to call [`render_dependencies()`](#TODO):
|
|
||||||
- Using the [`ComponentDependencyMiddleware`](#TODO) middleware
|
|
||||||
- Rendering the HTML by calling [`Component.render()`](#TODO) with `render_dependencies=True` (default)
|
|
||||||
- Rendering the HTML by calling [`Component.render_to_response()`](#TODO) (always renders dependencies)
|
|
||||||
- Directly passing rendered HTML to [`render_dependencies()`](#TODO)
|
|
||||||
3. If you pre-render one component to pass it into another, the pre-rendered component must be rendered with
|
|
||||||
[`render_dependencies=False`](#TODO).
|
|
||||||
|
|
||||||
## Dependencies strategies
|
## Dependencies strategies
|
||||||
|
|
||||||
The rendered HTML may be used in different contexts (browser, email, etc).
|
The rendered HTML may be used in different contexts (browser, email, etc).
|
||||||
If your components use JS and CSS scripts, you may need to handle them differently.
|
If your components use JS and CSS scripts, you may need to handle them differently.
|
||||||
|
|
||||||
|
The different ways for handling JS / CSS are called **"dependencies strategies"**.
|
||||||
|
|
||||||
[`render()`](../../../reference/api/#django_components.Component.render) and [`render_to_response()`](../../../reference/api/#django_components.Component.render_to_response)
|
[`render()`](../../../reference/api/#django_components.Component.render) and [`render_to_response()`](../../../reference/api/#django_components.Component.render_to_response)
|
||||||
accept a `deps_strategy` parameter, which controls where and how the JS / CSS are inserted into the HTML.
|
accept a `deps_strategy` parameter, which controls where and how the JS / CSS are inserted into the HTML.
|
||||||
|
|
||||||
|
```python
|
||||||
|
main_page = MainPage.render(deps_strategy="document")
|
||||||
|
fragment = MyComponent.render_to_response(deps_strategy="fragment")
|
||||||
|
```
|
||||||
|
|
||||||
The `deps_strategy` parameter is set at the root of a component render tree, which is why it is not available for
|
The `deps_strategy` parameter is set at the root of a component render tree, which is why it is not available for
|
||||||
the [`{% component %}`](../../../reference/template_tags#component) tag.
|
the [`{% component %}`](../../../reference/template_tags#component) tag.
|
||||||
|
|
||||||
|
When you use Django's [`django.shortcuts.render()`](https://docs.djangoproject.com/en/5.1/topics/http/shortcuts/#render)
|
||||||
|
or [`Template.render()`](https://docs.djangoproject.com/en/5.1/ref/templates/api/#django.template.Template.render) to render templates,
|
||||||
|
you can't directly set the `deps_strategy` parameter.
|
||||||
|
|
||||||
|
In this case, you can set the `deps_strategy` with the `DJC_DEPS_STRATEGY` context variable.
|
||||||
|
|
||||||
|
```python
|
||||||
|
from django.template.context import Context
|
||||||
|
from django.shortcuts import render
|
||||||
|
|
||||||
|
ctx = Context({"DJC_DEPS_STRATEGY": "fragment"})
|
||||||
|
fragment = render(request, "my_component.html", ctx=ctx)
|
||||||
|
```
|
||||||
|
|
||||||
!!! info
|
!!! info
|
||||||
|
|
||||||
The `deps_strategy` parameter is ultimately passed to [`render_dependencies()`](../../../reference/api/#django_components.render_dependencies).
|
The `deps_strategy` parameter is ultimately passed to [`render_dependencies()`](../../../reference/api/#django_components.render_dependencies).
|
||||||
|
|
||||||
There are five dependencies strategies:
|
!!! note "Why is `deps_strategy` required?"
|
||||||
|
|
||||||
|
This is a technical limitation of the current implementation.
|
||||||
|
|
||||||
|
When a component is rendered, django-components embeds metadata about the component's JS and CSS into the HTML.
|
||||||
|
|
||||||
|
This way we can compose components together, and know which JS / CSS dependencies are needed.
|
||||||
|
|
||||||
|
As the last step of rendering, django-components extracts this metadata and uses a selected strategy
|
||||||
|
to insert the JS / CSS into the HTML.
|
||||||
|
|
||||||
|
There are six dependencies strategies:
|
||||||
|
|
||||||
- [`document`](../../advanced/rendering_js_css#document) (default)
|
- [`document`](../../advanced/rendering_js_css#document) (default)
|
||||||
- Smartly inserts JS / CSS into placeholders or into `<head>` and `<body>` tags.
|
- Smartly inserts JS / CSS into placeholders or into `<head>` and `<body>` tags.
|
||||||
|
@ -254,6 +156,10 @@ There are five dependencies strategies:
|
||||||
- [`append`](../../advanced/rendering_js_css#append)
|
- [`append`](../../advanced/rendering_js_css#append)
|
||||||
- Insert JS / CSS after the rendered HTML.
|
- Insert JS / CSS after the rendered HTML.
|
||||||
- No extra script loaded.
|
- No extra script loaded.
|
||||||
|
- [`ignore`](../../advanced/rendering_js_css#ignore)
|
||||||
|
- HTML is left as-is. You can still process it with a different strategy later with
|
||||||
|
[`render_dependencies()`](../../../reference/api/#django_components.render_dependencies).
|
||||||
|
- Used for inserting rendered HTML into other components.
|
||||||
|
|
||||||
### `document`
|
### `document`
|
||||||
|
|
||||||
|
@ -290,7 +196,7 @@ the page in the browser:
|
||||||
</script>
|
</script>
|
||||||
<style>
|
<style>
|
||||||
.button {
|
.button {
|
||||||
background-color: blue;
|
background-color: blue;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
```
|
```
|
||||||
|
@ -420,3 +326,106 @@ JS and CSS is **always** inserted after the rendered content.
|
||||||
**Included scripts:**
|
**Included scripts:**
|
||||||
|
|
||||||
Same as for the [`"simple"`](#simple) strategy.
|
Same as for the [`"simple"`](#simple) strategy.
|
||||||
|
|
||||||
|
### `ignore`
|
||||||
|
|
||||||
|
`deps_strategy="ignore"` is used when you do NOT want to process JS and CSS of the rendered HTML.
|
||||||
|
|
||||||
|
```python
|
||||||
|
html = MyComponent.render(deps_strategy="ignore")
|
||||||
|
```
|
||||||
|
|
||||||
|
The rendered HTML is left as-is. You can still process it with a different strategy later with `render_dependencies()`.
|
||||||
|
|
||||||
|
This is useful when you want to insert rendered HTML into another component.
|
||||||
|
|
||||||
|
```python
|
||||||
|
html = MyComponent.render(deps_strategy="ignore")
|
||||||
|
html = AnotherComponent.render(slots={"content": html})
|
||||||
|
```
|
||||||
|
|
||||||
|
## Manually rendering JS / CSS
|
||||||
|
|
||||||
|
When rendering templates or components, django-components covers all the traditional ways how components
|
||||||
|
or templates can be rendered:
|
||||||
|
|
||||||
|
- [`Component.render()`](../../../reference/api/#django_components.Component.render)
|
||||||
|
- [`Component.render_to_response()`](../../../reference/api/#django_components.Component.render_to_response)
|
||||||
|
- [`Template.render()`](https://docs.djangoproject.com/en/5.1/ref/templates/api/#django.template.Template.render)
|
||||||
|
- [`django.shortcuts.render()`](https://docs.djangoproject.com/en/5.1/topics/http/shortcuts/#render)
|
||||||
|
|
||||||
|
This way you don't need to manually handle rendering of JS / CSS.
|
||||||
|
|
||||||
|
However, for advanced or low-level use cases, you may need to control when to render JS / CSS.
|
||||||
|
|
||||||
|
In such case you can directly pass rendered HTML to [`render_dependencies()`](../../../reference/api/#django_components.render_dependencies).
|
||||||
|
|
||||||
|
This function will extract all used components in the HTML string, and insert the components' JS and CSS
|
||||||
|
based on given strategy.
|
||||||
|
|
||||||
|
!!! info
|
||||||
|
|
||||||
|
The truth is that all the methods listed above call [`render_dependencies()`](../../../reference/api/#django_components.render_dependencies)
|
||||||
|
internally.
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
|
||||||
|
To see how [`render_dependencies()`](../../../reference/api/#django_components.render_dependencies) works,
|
||||||
|
let's render a template with a component.
|
||||||
|
|
||||||
|
We will render it twice:
|
||||||
|
|
||||||
|
- First time, we let `template.render()` handle the rendering.
|
||||||
|
- Second time, we prevent `template.render()` from inserting the component's JS and CSS with `deps_strategy="ignore"`.
|
||||||
|
|
||||||
|
Instead, we pass the "unprocessed" HTML to `render_dependencies()` ourselves to insert the component's JS and CSS.
|
||||||
|
|
||||||
|
```python
|
||||||
|
from django.template.base import Template
|
||||||
|
from django.template.context import Context
|
||||||
|
from django_components import render_dependencies
|
||||||
|
|
||||||
|
template = Template("""
|
||||||
|
{% load component_tags %}
|
||||||
|
<!doctype html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>MyPage</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<main>
|
||||||
|
{% component "my_button" %}
|
||||||
|
Click me!
|
||||||
|
{% endcomponent %}
|
||||||
|
</main>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
""")
|
||||||
|
|
||||||
|
rendered = template.render(Context({}))
|
||||||
|
|
||||||
|
rendered2_raw = template.render(Context({"DJC_DEPS_STRATEGY": "ignore"}))
|
||||||
|
rendered2 = render_dependencies(rendered2_raw)
|
||||||
|
|
||||||
|
assert rendered == rendered2
|
||||||
|
```
|
||||||
|
|
||||||
|
Same applies to other strategies and other methods of rendering:
|
||||||
|
|
||||||
|
```python
|
||||||
|
raw_html = MyComponent.render(deps_strategy="ignore")
|
||||||
|
html = render_dependencies(raw_html, deps_strategy="document")
|
||||||
|
|
||||||
|
html2 = MyComponent.render(deps_strategy="document")
|
||||||
|
|
||||||
|
assert html == html2
|
||||||
|
```
|
||||||
|
|
||||||
|
## HTML fragments
|
||||||
|
|
||||||
|
Django-components provides a seamless integration with HTML fragments with AJAX ([HTML over the wire](https://hotwired.dev/)),
|
||||||
|
whether you're using jQuery, HTMX, AlpineJS, vanilla JavaScript, or other.
|
||||||
|
|
||||||
|
This is achieved by the combination of the [`"document"`](#document) and [`"fragment"`](#fragment) strategies.
|
||||||
|
|
||||||
|
Read more about [HTML fragments](../../advanced/html_fragments).
|
||||||
|
|
|
@ -247,7 +247,6 @@ Button.render(
|
||||||
- `deps_strategy` - Dependencies rendering strategy (default: `"document"`)
|
- `deps_strategy` - Dependencies rendering strategy (default: `"document"`)
|
||||||
- `request` - HTTP request object, used for context processors (optional)
|
- `request` - HTTP request object, used for context processors (optional)
|
||||||
- `escape_slots_content` - Whether to HTML-escape slot content (default: `True`)
|
- `escape_slots_content` - Whether to HTML-escape slot content (default: `True`)
|
||||||
- `render_dependencies` - Whether to process JS and CSS dependencies (default: `True`)
|
|
||||||
|
|
||||||
All arguments are optional. If not provided, they default to empty values or sensible defaults.
|
All arguments are optional. If not provided, they default to empty values or sensible defaults.
|
||||||
|
|
||||||
|
@ -347,7 +346,7 @@ The `deps_strategy` parameter is ultimately passed to [`render_dependencies()`](
|
||||||
|
|
||||||
Learn more about [Rendering JS / CSS](../../advanced/rendering_js_css).
|
Learn more about [Rendering JS / CSS](../../advanced/rendering_js_css).
|
||||||
|
|
||||||
There are five dependencies rendering strategies:
|
There are six dependencies rendering strategies:
|
||||||
|
|
||||||
- [`document`](../../advanced/rendering_js_css#document) (default)
|
- [`document`](../../advanced/rendering_js_css#document) (default)
|
||||||
- Smartly inserts JS / CSS into placeholders ([`{% component_js_dependencies %}`](../../../reference/template_tags#component_js_dependencies)) or into `<head>` and `<body>` tags.
|
- Smartly inserts JS / CSS into placeholders ([`{% component_js_dependencies %}`](../../../reference/template_tags#component_js_dependencies)) or into `<head>` and `<body>` tags.
|
||||||
|
@ -368,6 +367,10 @@ There are five dependencies rendering strategies:
|
||||||
- Insert JS / CSS after the rendered HTML.
|
- Insert JS / CSS after the rendered HTML.
|
||||||
- Ignores the placeholders ([`{% component_js_dependencies %}`](../../../reference/template_tags#component_js_dependencies)) and any `<head>`/`<body>` HTML tags.
|
- Ignores the placeholders ([`{% component_js_dependencies %}`](../../../reference/template_tags#component_js_dependencies)) and any `<head>`/`<body>` HTML tags.
|
||||||
- No extra script loaded.
|
- No extra script loaded.
|
||||||
|
- [`ignore`](../../advanced/rendering_js_css#ignore)
|
||||||
|
- HTML is left as-is. You can still process it with a different strategy later with
|
||||||
|
[`render_dependencies()`](../../../reference/api/#django_components.render_dependencies).
|
||||||
|
- Used for inserting rendered HTML into other components.
|
||||||
|
|
||||||
!!! info
|
!!! info
|
||||||
|
|
||||||
|
@ -434,7 +437,7 @@ class Button(Component):
|
||||||
icon = Icon.render(
|
icon = Icon.render(
|
||||||
context=context,
|
context=context,
|
||||||
args=["icon-name"],
|
args=["icon-name"],
|
||||||
render_dependencies=False,
|
deps_strategy="ignore",
|
||||||
)
|
)
|
||||||
# Update context with icon
|
# Update context with icon
|
||||||
with context.update({"icon": icon}):
|
with context.update({"icon": icon}):
|
||||||
|
@ -515,9 +518,10 @@ Button.render(
|
||||||
)
|
)
|
||||||
```
|
```
|
||||||
|
|
||||||
## Nesting components
|
## Components as input
|
||||||
|
|
||||||
You can nest components by rendering one component and using its output as input to another:
|
django_components makes it possible to compose components in a "React-like" way,
|
||||||
|
where you can render one component and use its output as input to another component:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
from django.utils.safestring import mark_safe
|
from django.utils.safestring import mark_safe
|
||||||
|
@ -525,7 +529,7 @@ from django.utils.safestring import mark_safe
|
||||||
# Render the inner component
|
# Render the inner component
|
||||||
inner_html = InnerComponent.render(
|
inner_html = InnerComponent.render(
|
||||||
kwargs={"some_data": "value"},
|
kwargs={"some_data": "value"},
|
||||||
render_dependencies=False, # Important for nesting!
|
deps_strategy="ignore", # Important for nesting!
|
||||||
)
|
)
|
||||||
|
|
||||||
# Use inner component's output in the outer component
|
# Use inner component's output in the outer component
|
||||||
|
@ -536,9 +540,10 @@ outer_html = OuterComponent.render(
|
||||||
)
|
)
|
||||||
```
|
```
|
||||||
|
|
||||||
The key here is setting `render_dependencies=False` for the inner component. This prevents duplicate dependencies when the outer component is rendered.
|
The key here is setting [`deps_strategy="ignore"`](../../advanced/rendering_js_css#ignore) for the inner component. This prevents duplicate
|
||||||
|
rendering of JS / CSS dependencies when the outer component is rendered.
|
||||||
|
|
||||||
When `render_dependencies=False`:
|
When `deps_strategy="ignore"`:
|
||||||
|
|
||||||
- No JS or CSS dependencies will be added to the output HTML
|
- No JS or CSS dependencies will be added to the output HTML
|
||||||
- The component's content is rendered as-is
|
- The component's content is rendered as-is
|
||||||
|
@ -582,7 +587,7 @@ DynamicComponent.render(
|
||||||
},
|
},
|
||||||
slots={
|
slots={
|
||||||
"pagination": PaginationComponent.render(
|
"pagination": PaginationComponent.render(
|
||||||
render_dependencies=False,
|
deps_strategy="ignore",
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
@ -611,3 +616,13 @@ After which you will be able to use the dynamic component with the new name:
|
||||||
{% endfill %}
|
{% endfill %}
|
||||||
{% endcomponent %}
|
{% endcomponent %}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## HTML fragments
|
||||||
|
|
||||||
|
Django-components provides a seamless integration with HTML fragments with AJAX ([HTML over the wire](https://hotwired.dev/)),
|
||||||
|
whether you're using jQuery, HTMX, AlpineJS, vanilla JavaScript, or other.
|
||||||
|
|
||||||
|
This is achieved by the combination of the [`"document"`](../../advanced/rendering_js_css#document)
|
||||||
|
and [`"fragment"`](../../advanced/rendering_js_css#fragment) dependencies rendering strategies.
|
||||||
|
|
||||||
|
Read more about [HTML fragments](../../advanced/html_fragments) and [Rendering JS / CSS](../../advanced/rendering_js_css).
|
||||||
|
|
|
@ -52,7 +52,8 @@ Be sure to prefix your rules with unique CSS class like `calendar`, so the CSS d
|
||||||
|
|
||||||
This CSS will be inserted into the page as an inlined `<style>` tag, at the position defined by
|
This CSS will be inserted into the page as an inlined `<style>` tag, at the position defined by
|
||||||
[`{% component_css_dependencies %}`](../../reference/template_tags#component_css_dependencies),
|
[`{% component_css_dependencies %}`](../../reference/template_tags#component_css_dependencies),
|
||||||
or at the end of the inside the `<head>` tag (See [JS and CSS output locations](../../concepts/advanced/rendering_js_css/#js-and-css-output-locations)).
|
or at the end of the inside the `<head>` tag
|
||||||
|
(See [Default JS / CSS locations](../../concepts/advanced/rendering_js_css#default-js-css-locations)).
|
||||||
|
|
||||||
So in your HTML, you may see something like this:
|
So in your HTML, you may see something like this:
|
||||||
|
|
||||||
|
@ -103,7 +104,7 @@ This makes all variables defined only be defined inside this component and not a
|
||||||
|
|
||||||
Similarly to CSS, JS will be inserted into the page as an inlined `<script>` tag, at the position defined by
|
Similarly to CSS, JS will be inserted into the page as an inlined `<script>` tag, at the position defined by
|
||||||
[`{% component_js_dependencies %}`](../../reference/template_tags#component_js_dependencies),
|
[`{% component_js_dependencies %}`](../../reference/template_tags#component_js_dependencies),
|
||||||
or at the end of the inside the `<body>` tag (See [JS and CSS output locations](../../concepts/advanced/rendering_js_css/#js-and-css-output-locations)).
|
or at the end of the inside the `<body>` tag (See [Default JS / CSS locations](../../concepts/advanced/rendering_js_css#default-js-css-locations)).
|
||||||
|
|
||||||
So in your HTML, you may see something like this:
|
So in your HTML, you may see something like this:
|
||||||
|
|
||||||
|
|
|
@ -159,7 +159,8 @@ and keeping your CSS and Javascript in the static directory.
|
||||||
Remember that you can use
|
Remember that you can use
|
||||||
[`{% component_js_dependencies %}`](../../reference/template_tags#component_js_dependencies)
|
[`{% component_js_dependencies %}`](../../reference/template_tags#component_js_dependencies)
|
||||||
and [`{% component_css_dependencies %}`](../../reference/template_tags#component_css_dependencies)
|
and [`{% component_css_dependencies %}`](../../reference/template_tags#component_css_dependencies)
|
||||||
to change where the `<script>` and `<style>` tags will be rendered (See [JS and CSS output locations](../../concepts/advanced/rendering_js_css/#js-and-css-output-locations)).
|
to change where the `<script>` and `<style>` tags will be rendered
|
||||||
|
(See [Default JS / CSS locations](../../concepts/advanced/rendering_js_css#default-js-css-locations)).
|
||||||
|
|
||||||
!!! info
|
!!! info
|
||||||
|
|
||||||
|
|
|
@ -156,15 +156,17 @@ This is how we achieve that:
|
||||||
|
|
||||||
There's multiple ways to achieve this:
|
There's multiple ways to achieve this:
|
||||||
|
|
||||||
- The approach recommended to the users is to use the `ComponentDependencyMiddleware` middleware, which scans all outgoing HTML, and post-processes the `<!-- _RENDERED -->` comments.
|
|
||||||
|
|
||||||
- If users are using `Component.render()` or `Component.render_to_response()`, these post-process the `<!-- _RENDERED -->` comments by default.
|
- If users are using `Component.render()` or `Component.render_to_response()`, these post-process the `<!-- _RENDERED -->` comments by default.
|
||||||
|
|
||||||
- NOTE: Users are able to opt out of the post-processing by setting `render_dependencies=False`.
|
- NOTE: Users are able to opt out of the post-processing by setting `deps_strategy="ignore"`.
|
||||||
|
|
||||||
- For advanced use cases, users may use `render_dependencies()` directly. This is the function that both `ComponentDependencyMiddleware` and `Component.render()` call internally.
|
- If one renders a Template directly, the `<!-- _RENDERED -->` will be processed too. We achieve this by
|
||||||
|
modifying Django's `Template.render()` method.
|
||||||
|
|
||||||
`render_dependencies()`, whether called directly, via middleware or other way, does the following:
|
- For advanced use cases, users may use `render_dependencies()` directly. This is the function that
|
||||||
|
`Component.render()` calls internally.
|
||||||
|
|
||||||
|
`render_dependencies()`, whether called directly, or other way, does the following:
|
||||||
|
|
||||||
1. Find all `<!-- _RENDERED -->` comments, and for each comment:
|
1. Find all `<!-- _RENDERED -->` comments, and for each comment:
|
||||||
|
|
||||||
|
|
|
@ -1,12 +1,15 @@
|
||||||
|
### Basic installation
|
||||||
|
|
||||||
1. Install `django_components` into your environment:
|
1. Install `django_components` into your environment:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pip install django_components
|
pip install django_components
|
||||||
```
|
```
|
||||||
|
|
||||||
2. Load `django_components` into Django by adding it into `INSTALLED_APPS` in in your settings file:
|
2. Load `django_components` into Django by adding it into `INSTALLED_APPS` in your settings file:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
|
# app/settings.py
|
||||||
INSTALLED_APPS = [
|
INSTALLED_APPS = [
|
||||||
...,
|
...,
|
||||||
'django_components',
|
'django_components',
|
||||||
|
@ -16,43 +19,13 @@
|
||||||
3. `BASE_DIR` setting is required. Ensure that it is defined:
|
3. `BASE_DIR` setting is required. Ensure that it is defined:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
|
# app/settings.py
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
BASE_DIR = Path(__file__).resolve().parent.parent
|
BASE_DIR = Path(__file__).resolve().parent.parent
|
||||||
```
|
```
|
||||||
|
|
||||||
4. _Optional._ Set [`COMPONENTS.dirs`](../reference/settings.md#django_components.app_settings.ComponentsSettings.dirs)
|
4. Next, modify `TEMPLATES` section of `settings.py` as follows:
|
||||||
and/or [`COMPONENTS.app_dirs`](../reference/settings.md#django_components.app_settings.ComponentsSettings.app_dirs)
|
|
||||||
so django_components knows where to find component HTML, JS and CSS files:
|
|
||||||
|
|
||||||
If [`COMPONENTS.dirs`](../reference/settings.md#django_components.app_settings.ComponentsSettings.dirs)
|
|
||||||
is omitted, django-components will by default look for a top-level `/components` directory,
|
|
||||||
`{BASE_DIR}/components`.
|
|
||||||
|
|
||||||
```python
|
|
||||||
from django_components import ComponentsSettings
|
|
||||||
|
|
||||||
COMPONENTS = ComponentsSettings(
|
|
||||||
dirs=[
|
|
||||||
...,
|
|
||||||
Path(BASE_DIR) / "components",
|
|
||||||
],
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
||||||
In addition to [`COMPONENTS.dirs`](../reference/settings.md#django_components.app_settings.ComponentsSettings.dirs),
|
|
||||||
django_components will also load components from app-level directories, such as `my-app/components/`.
|
|
||||||
The directories within apps are configured with
|
|
||||||
[`COMPONENTS.app_dirs`](../reference/settings.md#django_components.app_settings.ComponentsSettings.app_dirs),
|
|
||||||
and the default is `[app]/components`.
|
|
||||||
|
|
||||||
!!! note
|
|
||||||
|
|
||||||
The input to [`COMPONENTS.dirs`](../reference/settings.md#django_components.app_settings.ComponentsSettings.dirs)
|
|
||||||
is the same as for `STATICFILES_DIRS`, and the paths must be full paths.
|
|
||||||
[See Django docs](https://docs.djangoproject.com/en/5.0/ref/settings/#staticfiles-dirs).
|
|
||||||
|
|
||||||
5. Next, modify `TEMPLATES` section of settings.py as follows:
|
|
||||||
|
|
||||||
- _Remove `'APP_DIRS': True,`_
|
- _Remove `'APP_DIRS': True,`_
|
||||||
- NOTE: Instead of `APP_DIRS: True`, we will use
|
- NOTE: Instead of `APP_DIRS: True`, we will use
|
||||||
|
@ -67,6 +40,7 @@
|
||||||
{
|
{
|
||||||
...,
|
...,
|
||||||
'OPTIONS': {
|
'OPTIONS': {
|
||||||
|
...,
|
||||||
'loaders':[(
|
'loaders':[(
|
||||||
'django.template.loaders.cached.Loader', [
|
'django.template.loaders.cached.Loader', [
|
||||||
# Default Django loader
|
# Default Django loader
|
||||||
|
@ -82,6 +56,20 @@
|
||||||
]
|
]
|
||||||
```
|
```
|
||||||
|
|
||||||
|
5. Add django-component's URL paths to your `urlpatterns`:
|
||||||
|
|
||||||
|
Django components already prefixes all URLs with `components/`. So when you are
|
||||||
|
adding the URLs to `urlpatterns`, you can use an empty string as the first argument:
|
||||||
|
|
||||||
|
```python
|
||||||
|
from django.urls import include, path
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
...
|
||||||
|
path("", include("django_components.urls")),
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
## Adding support for JS and CSS
|
## Adding support for JS and CSS
|
||||||
|
|
||||||
If you want to use JS or CSS with components, you will need to:
|
If you want to use JS or CSS with components, you will need to:
|
||||||
|
@ -100,38 +88,13 @@ If you want to use JS or CSS with components, you will need to:
|
||||||
]
|
]
|
||||||
```
|
```
|
||||||
|
|
||||||
2. Add [`ComponentDependencyMiddleware`](../reference/middlewares.md#django_components.dependencies.ComponentDependencyMiddleware)
|
|
||||||
to `MIDDLEWARE` setting.
|
|
||||||
|
|
||||||
The middleware searches the outgoing HTML for all components that were rendered
|
2. _Optional._ If you want to change where the JS and CSS is rendered, use
|
||||||
to generate the HTML, and adds the JS and CSS associated with those components.
|
|
||||||
|
|
||||||
```python
|
|
||||||
MIDDLEWARE = [
|
|
||||||
...
|
|
||||||
"django_components.middleware.ComponentDependencyMiddleware",
|
|
||||||
]
|
|
||||||
```
|
|
||||||
|
|
||||||
Read more in [Rendering JS/CSS dependencies](../concepts/advanced/rendering_js_css.md).
|
|
||||||
|
|
||||||
3. Add django-component's URL paths to your `urlpatterns`:
|
|
||||||
|
|
||||||
```python
|
|
||||||
from django.urls import include, path
|
|
||||||
|
|
||||||
urlpatterns = [
|
|
||||||
...
|
|
||||||
path("", include("django_components.urls")),
|
|
||||||
]
|
|
||||||
```
|
|
||||||
|
|
||||||
4. _Optional._ If you want to change where the JS and CSS is rendered, use
|
|
||||||
[`{% component_js_dependencies %}`](../reference/template_tags.md#component_css_dependencies)
|
[`{% component_js_dependencies %}`](../reference/template_tags.md#component_css_dependencies)
|
||||||
and [`{% component_css_dependencies %}`](../reference/template_tags.md#component_js_dependencies).
|
and [`{% component_css_dependencies %}`](../reference/template_tags.md#component_js_dependencies).
|
||||||
|
|
||||||
By default, the JS `<script>` and CSS `<link>` tags are automatically inserted
|
By default, the JS `<script>` and CSS `<link>` tags are automatically inserted
|
||||||
into the HTML (See [JS and CSS output locations](../../concepts/advanced/rendering_js_css/#js-and-css-output-locations)).
|
into the HTML (See [Default JS / CSS locations](../../concepts/advanced/rendering_js_css/#default-js-css-locations)).
|
||||||
|
|
||||||
```django
|
```django
|
||||||
<!doctype html>
|
<!doctype html>
|
||||||
|
@ -147,7 +110,7 @@ If you want to use JS or CSS with components, you will need to:
|
||||||
</html>
|
</html>
|
||||||
```
|
```
|
||||||
|
|
||||||
5. _Optional._ By default, components' JS and CSS files are cached in memory.
|
3. _Optional._ By default, components' JS and CSS files are cached in memory.
|
||||||
|
|
||||||
If you want to change the cache backend, set the [`COMPONENTS.cache`](../reference/settings.md#django_components.app_settings.ComponentsSettings.cache) setting.
|
If you want to change the cache backend, set the [`COMPONENTS.cache`](../reference/settings.md#django_components.app_settings.ComponentsSettings.cache) setting.
|
||||||
|
|
||||||
|
@ -156,13 +119,15 @@ If you want to use JS or CSS with components, you will need to:
|
||||||
## Optional
|
## Optional
|
||||||
|
|
||||||
### Builtin template tags
|
### Builtin template tags
|
||||||
To avoid loading the app in each template using `{% load component_tags %}`, you can add the tag as a 'builtin' in settings.py
|
|
||||||
|
To avoid loading the app in each template using `{% load component_tags %}`, you can add the tag as a 'builtin' in `settings.py`:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
TEMPLATES = [
|
TEMPLATES = [
|
||||||
{
|
{
|
||||||
...,
|
...,
|
||||||
'OPTIONS': {
|
'OPTIONS': {
|
||||||
|
...,
|
||||||
'builtins': [
|
'builtins': [
|
||||||
'django_components.templatetags.component_tags',
|
'django_components.templatetags.component_tags',
|
||||||
]
|
]
|
||||||
|
@ -171,6 +136,48 @@ TEMPLATES = [
|
||||||
]
|
]
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Component directories
|
||||||
|
|
||||||
|
django-components needs to know where to search for component HTML, JS and CSS files.
|
||||||
|
|
||||||
|
There are two ways to configure the component directories:
|
||||||
|
|
||||||
|
- [`COMPONENTS.dirs`](../reference/settings.md#django_components.app_settings.ComponentsSettings.dirs) sets global component directories.
|
||||||
|
- [`COMPONENTS.app_dirs`](../reference/settings.md#django_components.app_settings.ComponentsSettings.app_dirs) sets app-level component directories.
|
||||||
|
|
||||||
|
By default, django-components will look for a top-level `/components` directory,
|
||||||
|
`{BASE_DIR}/components`, equivalent to:
|
||||||
|
|
||||||
|
```python
|
||||||
|
from django_components import ComponentsSettings
|
||||||
|
|
||||||
|
COMPONENTS = ComponentsSettings(
|
||||||
|
dirs=[
|
||||||
|
...,
|
||||||
|
Path(BASE_DIR) / "components",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
For app-level directories, the default is `[app]/components`, equivalent to:
|
||||||
|
|
||||||
|
```python
|
||||||
|
from django_components import ComponentsSettings
|
||||||
|
|
||||||
|
COMPONENTS = ComponentsSettings(
|
||||||
|
app_dirs=[
|
||||||
|
...,
|
||||||
|
"components",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
!!! note
|
||||||
|
|
||||||
|
The input to [`COMPONENTS.dirs`](../reference/settings.md#django_components.app_settings.ComponentsSettings.dirs)
|
||||||
|
is the same as for `STATICFILES_DIRS`, and the paths must be full paths.
|
||||||
|
[See Django docs](https://docs.djangoproject.com/en/5.0/ref/settings/#staticfiles-dirs).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
Now you're all set! Read on to find out how to build your first component.
|
Now you're all set! Read on to find out how to build your first component.
|
||||||
|
|
|
@ -7,7 +7,6 @@ nav:
|
||||||
- Extension commands: extension_commands.md
|
- Extension commands: extension_commands.md
|
||||||
- Extension hooks: extension_hooks.md
|
- Extension hooks: extension_hooks.md
|
||||||
- Extension URLs: extension_urls.md
|
- Extension URLs: extension_urls.md
|
||||||
- Middlewares: middlewares.md
|
|
||||||
- Settings: settings.md
|
- Settings: settings.md
|
||||||
- Signals: signals.md
|
- Signals: signals.md
|
||||||
- Tag formatters: tag_formatters.md
|
- Tag formatters: tag_formatters.md
|
||||||
|
|
|
@ -54,9 +54,7 @@ python manage.py components ext run <extension> <command>
|
||||||
## `components create`
|
## `components create`
|
||||||
|
|
||||||
```txt
|
```txt
|
||||||
usage: python manage.py components create [-h] [--path PATH] [--js JS] [--css CSS] [--template TEMPLATE] [--force] [--verbose]
|
usage: python manage.py components create [-h] [--path PATH] [--js JS] [--css CSS] [--template TEMPLATE] [--force] [--verbose] [--dry-run] name
|
||||||
[--dry-run]
|
|
||||||
name
|
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -238,7 +236,7 @@ List all extensions.
|
||||||
- `--columns COLUMNS`
|
- `--columns COLUMNS`
|
||||||
- Comma-separated list of columns to show. Available columns: name. Defaults to `--columns name`.
|
- Comma-separated list of columns to show. Available columns: name. Defaults to `--columns name`.
|
||||||
- `-s`, `--simple`
|
- `-s`, `--simple`
|
||||||
- Only show table data, without headers. Use this option for generating machine- readable output.
|
- Only show table data, without headers. Use this option for generating machine-readable output.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -401,7 +399,7 @@ List all components created in this project.
|
||||||
- `--columns COLUMNS`
|
- `--columns COLUMNS`
|
||||||
- Comma-separated list of columns to show. Available columns: name, full_name, path. Defaults to `--columns full_name,path`.
|
- Comma-separated list of columns to show. Available columns: name, full_name, path. Defaults to `--columns full_name,path`.
|
||||||
- `-s`, `--simple`
|
- `-s`, `--simple`
|
||||||
- Only show table data, without headers. Use this option for generating machine- readable output.
|
- Only show table data, without headers. Use this option for generating machine-readable output.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -463,9 +461,8 @@ ProjectDashboardAction project.components.dashboard_action.ProjectDashboardAc
|
||||||
## `upgradecomponent`
|
## `upgradecomponent`
|
||||||
|
|
||||||
```txt
|
```txt
|
||||||
usage: upgradecomponent [-h] [--path PATH] [--version] [-v {0,1,2,3}] [--settings SETTINGS]
|
usage: upgradecomponent [-h] [--path PATH] [--version] [-v {0,1,2,3}] [--settings SETTINGS] [--pythonpath PYTHONPATH] [--traceback] [--no-color]
|
||||||
[--pythonpath PYTHONPATH] [--traceback] [--no-color] [--force-color]
|
[--force-color] [--skip-checks]
|
||||||
[--skip-checks]
|
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -509,10 +506,8 @@ Deprecated. Use `components upgrade` instead.
|
||||||
## `startcomponent`
|
## `startcomponent`
|
||||||
|
|
||||||
```txt
|
```txt
|
||||||
usage: startcomponent [-h] [--path PATH] [--js JS] [--css CSS] [--template TEMPLATE] [--force]
|
usage: startcomponent [-h] [--path PATH] [--js JS] [--css CSS] [--template TEMPLATE] [--force] [--verbose] [--dry-run] [--version] [-v {0,1,2,3}]
|
||||||
[--verbose] [--dry-run] [--version] [-v {0,1,2,3}] [--settings SETTINGS]
|
[--settings SETTINGS] [--pythonpath PYTHONPATH] [--traceback] [--no-color] [--force-color] [--skip-checks]
|
||||||
[--pythonpath PYTHONPATH] [--traceback] [--no-color] [--force-color]
|
|
||||||
[--skip-checks]
|
|
||||||
name
|
name
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
|
@ -1,16 +0,0 @@
|
||||||
<!-- Autogenerated by reference.py -->
|
|
||||||
|
|
||||||
# Middlewares
|
|
||||||
|
|
||||||
|
|
||||||
::: django_components.dependencies.ComponentDependencyMiddleware
|
|
||||||
options:
|
|
||||||
inherited_members: false
|
|
||||||
show_root_heading: true
|
|
||||||
show_signature: false
|
|
||||||
separate_signature: false
|
|
||||||
show_symbol_type_heading: false
|
|
||||||
show_symbol_type_toc: false
|
|
||||||
show_if_no_docstring: true
|
|
||||||
show_labels: false
|
|
||||||
|
|
|
@ -20,7 +20,7 @@ Import as
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<a href="https://github.com/django-components/django-components/tree/master/src/django_components/templatetags/component_tags.py#L1069" target="_blank">See source code</a>
|
<a href="https://github.com/django-components/django-components/tree/master/src/django_components/templatetags/component_tags.py#L1024" target="_blank">See source code</a>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -30,7 +30,7 @@ Generally, this should be inserted into the `<head>` tag of the HTML.
|
||||||
|
|
||||||
If the generated HTML does NOT contain any `{% component_css_dependencies %}` tags, CSS links
|
If the generated HTML does NOT contain any `{% component_css_dependencies %}` tags, CSS links
|
||||||
are by default inserted into the `<head>` tag of the HTML. (See
|
are by default inserted into the `<head>` tag of the HTML. (See
|
||||||
[JS and CSS output locations](../../concepts/advanced/rendering_js_css/#js-and-css-output-locations))
|
[Default JS / CSS locations](../../concepts/advanced/rendering_js_css/#default-js-css-locations))
|
||||||
|
|
||||||
Note that there should be only one `{% component_css_dependencies %}` for the whole HTML document.
|
Note that there should be only one `{% component_css_dependencies %}` for the whole HTML document.
|
||||||
If you insert this tag multiple times, ALL CSS links will be duplicately inserted into ALL these places.
|
If you insert this tag multiple times, ALL CSS links will be duplicately inserted into ALL these places.
|
||||||
|
@ -43,7 +43,7 @@ If you insert this tag multiple times, ALL CSS links will be duplicately inserte
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<a href="https://github.com/django-components/django-components/tree/master/src/django_components/templatetags/component_tags.py#L1091" target="_blank">See source code</a>
|
<a href="https://github.com/django-components/django-components/tree/master/src/django_components/templatetags/component_tags.py#L1046" target="_blank">See source code</a>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -53,7 +53,7 @@ Generally, this should be inserted at the end of the `<body>` tag of the HTML.
|
||||||
|
|
||||||
If the generated HTML does NOT contain any `{% component_js_dependencies %}` tags, JS scripts
|
If the generated HTML does NOT contain any `{% component_js_dependencies %}` tags, JS scripts
|
||||||
are by default inserted at the end of the `<body>` tag of the HTML. (See
|
are by default inserted at the end of the `<body>` tag of the HTML. (See
|
||||||
[JS and CSS output locations](../../concepts/advanced/rendering_js_css/#js-and-css-output-locations))
|
[Default JS / CSS locations](../../concepts/advanced/rendering_js_css/#default-js-css-locations))
|
||||||
|
|
||||||
Note that there should be only one `{% component_js_dependencies %}` for the whole HTML document.
|
Note that there should be only one `{% component_js_dependencies %}` for the whole HTML document.
|
||||||
If you insert this tag multiple times, ALL JS scripts will be duplicately inserted into ALL these places.
|
If you insert this tag multiple times, ALL JS scripts will be duplicately inserted into ALL these places.
|
||||||
|
@ -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#L2791" target="_blank">See source code</a>
|
<a href="https://github.com/django-components/django-components/tree/master/src/django_components/templatetags/component_tags.py#L2785" target="_blank">See source code</a>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
"""
|
"""
|
||||||
Generate reference for all the different kinds of public API that we expose,
|
Generate reference for all the different kinds of public API that we expose,
|
||||||
like regular Python imports, middleware, template tags, settings, Django URLs, etc.
|
like regular Python imports, template tags, settings, Django URLs, etc.
|
||||||
|
|
||||||
All pages are generated inside `docs/reference/`.
|
All pages are generated inside `docs/reference/`.
|
||||||
|
|
||||||
|
@ -338,49 +338,6 @@ def _gen_default_settings_section(app_settings_filepath: str) -> str:
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def gen_reference_middlewares():
|
|
||||||
"""
|
|
||||||
Generate documentation for all available middleware of django-components,
|
|
||||||
as listed in module `django_components.middleware`.
|
|
||||||
"""
|
|
||||||
module = import_module("django_components.middleware")
|
|
||||||
|
|
||||||
preface = "<!-- Autogenerated by reference.py -->\n\n"
|
|
||||||
preface += (root / "docs/templates/reference_middlewares.md").read_text()
|
|
||||||
out_file = root / "docs/reference/middlewares.md"
|
|
||||||
|
|
||||||
out_file.parent.mkdir(parents=True, exist_ok=True)
|
|
||||||
with out_file.open("w", encoding="utf-8") as f:
|
|
||||||
f.write(preface + "\n\n")
|
|
||||||
|
|
||||||
for name, obj in inspect.getmembers(module):
|
|
||||||
if not inspect.isclass(obj):
|
|
||||||
continue
|
|
||||||
|
|
||||||
class_name = get_import_path(obj)
|
|
||||||
|
|
||||||
# For each entry, generate a mkdocstrings entry, e.g.
|
|
||||||
# ```
|
|
||||||
# ::: django_components.middleware.ComponentDependencyMiddleware
|
|
||||||
# options:
|
|
||||||
# ...
|
|
||||||
# ```
|
|
||||||
f.write(
|
|
||||||
f"::: {class_name}\n"
|
|
||||||
f" options:\n"
|
|
||||||
f" inherited_members: false\n"
|
|
||||||
f" show_root_heading: true\n"
|
|
||||||
f" show_signature: false\n"
|
|
||||||
f" separate_signature: false\n"
|
|
||||||
f" show_symbol_type_heading: false\n"
|
|
||||||
f" show_symbol_type_toc: false\n"
|
|
||||||
f" show_if_no_docstring: true\n"
|
|
||||||
f" show_labels: false\n"
|
|
||||||
)
|
|
||||||
|
|
||||||
f.write("\n")
|
|
||||||
|
|
||||||
|
|
||||||
def gen_reference_tagformatters():
|
def gen_reference_tagformatters():
|
||||||
"""
|
"""
|
||||||
Generate documentation for all pre-defined TagFormatters included
|
Generate documentation for all pre-defined TagFormatters included
|
||||||
|
@ -1145,7 +1102,6 @@ def gen_reference():
|
||||||
gen_reference_api()
|
gen_reference_api()
|
||||||
gen_reference_exceptions()
|
gen_reference_exceptions()
|
||||||
gen_reference_components()
|
gen_reference_components()
|
||||||
gen_reference_middlewares()
|
|
||||||
gen_reference_settings()
|
gen_reference_settings()
|
||||||
gen_reference_tagformatters()
|
gen_reference_tagformatters()
|
||||||
gen_reference_urls()
|
gen_reference_urls()
|
||||||
|
|
1
docs/templates/reference_middlewares.md
vendored
1
docs/templates/reference_middlewares.md
vendored
|
@ -1 +0,0 @@
|
||||||
# Middlewares
|
|
|
@ -46,7 +46,6 @@ MIDDLEWARE = [
|
||||||
"django.contrib.auth.middleware.AuthenticationMiddleware",
|
"django.contrib.auth.middleware.AuthenticationMiddleware",
|
||||||
"django.contrib.messages.middleware.MessageMiddleware",
|
"django.contrib.messages.middleware.MessageMiddleware",
|
||||||
"django.middleware.clickjacking.XFrameOptionsMiddleware",
|
"django.middleware.clickjacking.XFrameOptionsMiddleware",
|
||||||
"django_components.middleware.ComponentDependencyMiddleware",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
ROOT_URLCONF = "sampleproject.urls"
|
ROOT_URLCONF = "sampleproject.urls"
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
"""Main package for Django Components."""
|
"""Main package for Django Components."""
|
||||||
|
|
||||||
# Public API
|
# Public API
|
||||||
# NOTE: Middleware is exposed via django_components.middleware
|
|
||||||
# NOTE: Some of the documentation is generated based on these exports
|
# NOTE: Some of the documentation is generated based on these exports
|
||||||
# isort: off
|
# isort: off
|
||||||
from django_components.app_settings import ContextBehavior, ComponentsSettings
|
from django_components.app_settings import ContextBehavior, ComponentsSettings
|
||||||
|
|
|
@ -185,7 +185,9 @@ class ComponentInput:
|
||||||
# TODO_v1 - Remove, superseded by `deps_strategy`
|
# TODO_v1 - Remove, superseded by `deps_strategy`
|
||||||
type: DependenciesStrategy
|
type: DependenciesStrategy
|
||||||
"""Deprecated alias for `deps_strategy`."""
|
"""Deprecated alias for `deps_strategy`."""
|
||||||
|
# TODO_v1 - Remove, superseded by `deps_strategy`
|
||||||
render_dependencies: bool
|
render_dependencies: bool
|
||||||
|
"""Deprecated. Instead use `deps_strategy="ignore"`."""
|
||||||
|
|
||||||
|
|
||||||
@dataclass()
|
@dataclass()
|
||||||
|
@ -1999,6 +2001,7 @@ class Component(metaclass=ComponentMeta):
|
||||||
deps_strategy: DependenciesStrategy = "document",
|
deps_strategy: DependenciesStrategy = "document",
|
||||||
# TODO_v1 - Remove, superseded by `deps_strategy`
|
# TODO_v1 - Remove, superseded by `deps_strategy`
|
||||||
type: Optional[DependenciesStrategy] = None,
|
type: Optional[DependenciesStrategy] = None,
|
||||||
|
# TODO_v1 - Remove, superseded by `deps_strategy="ignore"`
|
||||||
render_dependencies: bool = True,
|
render_dependencies: bool = True,
|
||||||
request: Optional[HttpRequest] = None,
|
request: Optional[HttpRequest] = None,
|
||||||
**response_kwargs: Any,
|
**response_kwargs: Any,
|
||||||
|
@ -2063,6 +2066,7 @@ class Component(metaclass=ComponentMeta):
|
||||||
deps_strategy=deps_strategy,
|
deps_strategy=deps_strategy,
|
||||||
# TODO_v1 - Remove, superseded by `deps_strategy`
|
# TODO_v1 - Remove, superseded by `deps_strategy`
|
||||||
type=type,
|
type=type,
|
||||||
|
# TODO_v1 - Remove, superseded by `deps_strategy`
|
||||||
render_dependencies=render_dependencies,
|
render_dependencies=render_dependencies,
|
||||||
request=request,
|
request=request,
|
||||||
)
|
)
|
||||||
|
@ -2079,6 +2083,7 @@ class Component(metaclass=ComponentMeta):
|
||||||
deps_strategy: DependenciesStrategy = "document",
|
deps_strategy: DependenciesStrategy = "document",
|
||||||
# TODO_v1 - Remove, superseded by `deps_strategy`
|
# TODO_v1 - Remove, superseded by `deps_strategy`
|
||||||
type: Optional[DependenciesStrategy] = None,
|
type: Optional[DependenciesStrategy] = None,
|
||||||
|
# TODO_v1 - Remove, superseded by `deps_strategy="ignore"`
|
||||||
render_dependencies: bool = True,
|
render_dependencies: bool = True,
|
||||||
request: Optional[HttpRequest] = None,
|
request: Optional[HttpRequest] = None,
|
||||||
) -> str:
|
) -> str:
|
||||||
|
@ -2176,7 +2181,7 @@ class Component(metaclass=ComponentMeta):
|
||||||
icon = Icon.render(
|
icon = Icon.render(
|
||||||
context=context,
|
context=context,
|
||||||
args=["icon-name"],
|
args=["icon-name"],
|
||||||
render_dependencies=False,
|
deps_strategy="ignore",
|
||||||
)
|
)
|
||||||
# Update context with icon
|
# Update context with icon
|
||||||
with context.update({"icon": icon}):
|
with context.update({"icon": icon}):
|
||||||
|
@ -2194,7 +2199,7 @@ class Component(metaclass=ComponentMeta):
|
||||||
- `deps_strategy` - Optional. Configure how to handle JS and CSS dependencies. Read more about
|
- `deps_strategy` - Optional. Configure how to handle JS and CSS dependencies. Read more about
|
||||||
[Dependencies rendering](../../concepts/fundamentals/rendering_components#dependencies-rendering).
|
[Dependencies rendering](../../concepts/fundamentals/rendering_components#dependencies-rendering).
|
||||||
|
|
||||||
There are five strategies:
|
There are six strategies:
|
||||||
|
|
||||||
- [`"document"`](../../concepts/advanced/rendering_js_css#document) (default)
|
- [`"document"`](../../concepts/advanced/rendering_js_css#document) (default)
|
||||||
- Smartly inserts JS / CSS into placeholders or into `<head>` and `<body>` tags.
|
- Smartly inserts JS / CSS into placeholders or into `<head>` and `<body>` tags.
|
||||||
|
@ -2212,6 +2217,10 @@ class Component(metaclass=ComponentMeta):
|
||||||
- [`"append"`](../../concepts/advanced/rendering_js_css#append)
|
- [`"append"`](../../concepts/advanced/rendering_js_css#append)
|
||||||
- Insert JS / CSS after the rendered HTML.
|
- Insert JS / CSS after the rendered HTML.
|
||||||
- No extra script loaded.
|
- No extra script loaded.
|
||||||
|
- [`"ignore"`](../../concepts/advanced/rendering_js_css#ignore)
|
||||||
|
- HTML is left as-is. You can still process it with a different strategy later with
|
||||||
|
[`render_dependencies()`](../api/#django_components.render_dependencies).
|
||||||
|
- Used for inserting rendered HTML into other components.
|
||||||
|
|
||||||
- `request` - Optional. HTTPRequest object. Pass a request object directly to the component to apply
|
- `request` - Optional. HTTPRequest object. Pass a request object directly to the component to apply
|
||||||
[context processors](https://docs.djangoproject.com/en/5.2/ref/templates/api/#django.template.Context.update).
|
[context processors](https://docs.djangoproject.com/en/5.2/ref/templates/api/#django.template.Context.update).
|
||||||
|
@ -2222,22 +2231,6 @@ class Component(metaclass=ComponentMeta):
|
||||||
[`escape`](https://docs.djangoproject.com/en/5.2/ref/templates/builtins/#std-templatefilter-escape).
|
[`escape`](https://docs.djangoproject.com/en/5.2/ref/templates/builtins/#std-templatefilter-escape).
|
||||||
Defaults to `True`.
|
Defaults to `True`.
|
||||||
|
|
||||||
- `render_dependencies` - Optional. Whether the output should be processed to finalize JS and CSS dependencies.
|
|
||||||
Defaults to `True`.
|
|
||||||
|
|
||||||
Set this to `False` if you want to insert the resulting HTML into another component:
|
|
||||||
|
|
||||||
```py
|
|
||||||
html = Button.render(
|
|
||||||
render_dependencies=False,
|
|
||||||
)
|
|
||||||
|
|
||||||
# Insert the resulting HTML into another component
|
|
||||||
MyOtherComponent.render(
|
|
||||||
content=html,
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
||||||
**Type hints:**
|
**Type hints:**
|
||||||
|
|
||||||
`Component.render()` is NOT typed. To add type hints, you can wrap the inputs
|
`Component.render()` is NOT typed. To add type hints, you can wrap the inputs
|
||||||
|
@ -2295,8 +2288,12 @@ class Component(metaclass=ComponentMeta):
|
||||||
)
|
)
|
||||||
deps_strategy = type
|
deps_strategy = type
|
||||||
|
|
||||||
|
# TODO_v1 - Remove, superseded by `deps_strategy="ignore"`
|
||||||
|
if not render_dependencies:
|
||||||
|
deps_strategy = "ignore"
|
||||||
|
|
||||||
return comp._render_with_error_trace(
|
return comp._render_with_error_trace(
|
||||||
context, args, kwargs, slots, escape_slots_content, deps_strategy, render_dependencies, request
|
context, args, kwargs, slots, escape_slots_content, deps_strategy, request
|
||||||
)
|
)
|
||||||
|
|
||||||
# This is the internal entrypoint for the render function
|
# This is the internal entrypoint for the render function
|
||||||
|
@ -2308,14 +2305,11 @@ class Component(metaclass=ComponentMeta):
|
||||||
slots: Optional[Any] = None,
|
slots: Optional[Any] = None,
|
||||||
escape_slots_content: bool = True,
|
escape_slots_content: bool = True,
|
||||||
deps_strategy: DependenciesStrategy = "document",
|
deps_strategy: DependenciesStrategy = "document",
|
||||||
render_dependencies: bool = True,
|
|
||||||
request: Optional[HttpRequest] = None,
|
request: Optional[HttpRequest] = None,
|
||||||
) -> str:
|
) -> str:
|
||||||
# Modify the error to display full component path (incl. slots)
|
# Modify the error to display full component path (incl. slots)
|
||||||
with component_error_message([self.name]):
|
with component_error_message([self.name]):
|
||||||
return self._render_impl(
|
return self._render_impl(context, args, kwargs, slots, escape_slots_content, deps_strategy, request)
|
||||||
context, args, kwargs, slots, escape_slots_content, deps_strategy, render_dependencies, request
|
|
||||||
)
|
|
||||||
|
|
||||||
def _render_impl(
|
def _render_impl(
|
||||||
self,
|
self,
|
||||||
|
@ -2325,7 +2319,6 @@ class Component(metaclass=ComponentMeta):
|
||||||
slots: Optional[Any] = None,
|
slots: Optional[Any] = None,
|
||||||
escape_slots_content: bool = True,
|
escape_slots_content: bool = True,
|
||||||
deps_strategy: DependenciesStrategy = "document",
|
deps_strategy: DependenciesStrategy = "document",
|
||||||
render_dependencies: bool = True,
|
|
||||||
request: Optional[HttpRequest] = None,
|
request: Optional[HttpRequest] = None,
|
||||||
) -> str:
|
) -> str:
|
||||||
# Allow to pass down Request object via context.
|
# Allow to pass down Request object via context.
|
||||||
|
@ -2382,7 +2375,8 @@ class Component(metaclass=ComponentMeta):
|
||||||
deps_strategy=deps_strategy,
|
deps_strategy=deps_strategy,
|
||||||
# TODO_v1 - Remove, superseded by `deps_strategy`
|
# TODO_v1 - Remove, superseded by `deps_strategy`
|
||||||
type=deps_strategy,
|
type=deps_strategy,
|
||||||
render_dependencies=render_dependencies,
|
# TODO_v1 - Remove, superseded by `deps_strategy`
|
||||||
|
render_dependencies=deps_strategy != "ignore",
|
||||||
),
|
),
|
||||||
is_filled=None,
|
is_filled=None,
|
||||||
request=request,
|
request=request,
|
||||||
|
@ -2571,10 +2565,9 @@ class Component(metaclass=ComponentMeta):
|
||||||
post_render_callbacks[render_id] = on_component_rendered
|
post_render_callbacks[render_id] = on_component_rendered
|
||||||
|
|
||||||
# This is triggered after a full component tree was rendered, we resolve
|
# This is triggered after a full component tree was rendered, we resolve
|
||||||
# all inserted HTML comments into <script> and <link> tags (if render_dependencies=True)
|
# all inserted HTML comments into <script> and <link> tags.
|
||||||
def on_html_rendered(html: str) -> str:
|
def on_html_rendered(html: str) -> str:
|
||||||
if render_dependencies:
|
html = _render_dependencies(html, deps_strategy)
|
||||||
html = _render_dependencies(html, deps_strategy)
|
|
||||||
return html
|
return html
|
||||||
|
|
||||||
trace_component_msg(
|
trace_component_msg(
|
||||||
|
@ -2971,7 +2964,7 @@ class ComponentNode(BaseNode):
|
||||||
slots=slot_fills,
|
slots=slot_fills,
|
||||||
# NOTE: When we render components inside the template via template tags,
|
# NOTE: When we render components inside the template via template tags,
|
||||||
# do NOT render deps, because this may be decided by outer component
|
# do NOT render deps, because this may be decided by outer component
|
||||||
render_dependencies=False,
|
deps_strategy="ignore",
|
||||||
)
|
)
|
||||||
|
|
||||||
return output
|
return output
|
||||||
|
|
|
@ -59,7 +59,7 @@ class DynamicComponent(Component):
|
||||||
},
|
},
|
||||||
slots={
|
slots={
|
||||||
"pagination": PaginationComponent.render(
|
"pagination": PaginationComponent.render(
|
||||||
render_dependencies=False,
|
deps_strategy="ignore",
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
@ -143,7 +143,6 @@ class DynamicComponent(Component):
|
||||||
# was already escaped (if set so).
|
# was already escaped (if set so).
|
||||||
escape_slots_content=False,
|
escape_slots_content=False,
|
||||||
deps_strategy=self.input.deps_strategy,
|
deps_strategy=self.input.deps_strategy,
|
||||||
render_dependencies=self.input.render_dependencies,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
context["output"] = output
|
context["output"] = output
|
||||||
|
|
|
@ -11,6 +11,7 @@ from django.template import Context
|
||||||
from django_components.util.misc import get_last_index
|
from django_components.util.misc import get_last_index
|
||||||
|
|
||||||
_COMPONENT_CONTEXT_KEY = "_DJC_COMPONENT_CTX"
|
_COMPONENT_CONTEXT_KEY = "_DJC_COMPONENT_CTX"
|
||||||
|
_STRATEGY_CONTEXT_KEY = "DJC_DEPS_STRATEGY"
|
||||||
_INJECT_CONTEXT_KEY_PREFIX = "_DJC_INJECT__"
|
_INJECT_CONTEXT_KEY_PREFIX = "_DJC_INJECT__"
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,6 @@ import re
|
||||||
from hashlib import md5
|
from hashlib import md5
|
||||||
from typing import (
|
from typing import (
|
||||||
TYPE_CHECKING,
|
TYPE_CHECKING,
|
||||||
Callable,
|
|
||||||
Dict,
|
Dict,
|
||||||
List,
|
List,
|
||||||
Literal,
|
Literal,
|
||||||
|
@ -21,14 +20,11 @@ from typing import (
|
||||||
cast,
|
cast,
|
||||||
)
|
)
|
||||||
|
|
||||||
from asgiref.sync import iscoroutinefunction, markcoroutinefunction
|
|
||||||
from django.forms import Media
|
from django.forms import Media
|
||||||
from django.http import HttpRequest, HttpResponse, HttpResponseNotAllowed, HttpResponseNotFound, StreamingHttpResponse
|
from django.http import HttpRequest, HttpResponse, HttpResponseNotAllowed, HttpResponseNotFound
|
||||||
from django.http.response import HttpResponseBase
|
|
||||||
from django.template import Context, TemplateSyntaxError
|
from django.template import Context, TemplateSyntaxError
|
||||||
from django.templatetags.static import static
|
from django.templatetags.static import static
|
||||||
from django.urls import path, reverse
|
from django.urls import path, reverse
|
||||||
from django.utils.decorators import sync_and_async_middleware
|
|
||||||
from django.utils.safestring import SafeString, mark_safe
|
from django.utils.safestring import SafeString, mark_safe
|
||||||
from djc_core_html_parser import set_html_attributes
|
from djc_core_html_parser import set_html_attributes
|
||||||
|
|
||||||
|
@ -42,14 +38,14 @@ if TYPE_CHECKING:
|
||||||
|
|
||||||
|
|
||||||
ScriptType = Literal["css", "js"]
|
ScriptType = Literal["css", "js"]
|
||||||
DependenciesStrategy = Literal["document", "fragment", "simple", "prepend", "append"]
|
DependenciesStrategy = Literal["document", "fragment", "simple", "prepend", "append", "ignore"]
|
||||||
"""
|
"""
|
||||||
Type for the available strategies for rendering JS and CSS dependencies.
|
Type for the available strategies for rendering JS and CSS dependencies.
|
||||||
|
|
||||||
Read more about the [dependencies strategies](../../concepts/advanced/rendering_js_css).
|
Read more about the [dependencies strategies](../../concepts/advanced/rendering_js_css).
|
||||||
"""
|
"""
|
||||||
|
|
||||||
DEPS_STRATEGIES = ("document", "fragment", "simple", "prepend", "append")
|
DEPS_STRATEGIES = ("document", "fragment", "simple", "prepend", "append", "ignore")
|
||||||
|
|
||||||
|
|
||||||
#########################################################
|
#########################################################
|
||||||
|
@ -316,7 +312,7 @@ def insert_component_dependencies_comment(
|
||||||
) -> SafeString:
|
) -> SafeString:
|
||||||
"""
|
"""
|
||||||
Given some textual content, prepend it with a short string that
|
Given some textual content, prepend it with a short string that
|
||||||
will be used by the ComponentDependencyMiddleware to collect all
|
will be used by the `render_dependencies()` function to collect all
|
||||||
declared JS / CSS scripts.
|
declared JS / CSS scripts.
|
||||||
"""
|
"""
|
||||||
data = f"{component_cls.class_id},{component_id},{js_input_hash or ''},{css_input_hash or ''}"
|
data = f"{component_cls.class_id},{component_id},{js_input_hash or ''},{css_input_hash or ''}"
|
||||||
|
@ -413,6 +409,8 @@ def render_dependencies(content: TContent, strategy: DependenciesStrategy = "doc
|
||||||
"""
|
"""
|
||||||
if strategy not in DEPS_STRATEGIES:
|
if strategy not in DEPS_STRATEGIES:
|
||||||
raise ValueError(f"Invalid strategy '{strategy}'")
|
raise ValueError(f"Invalid strategy '{strategy}'")
|
||||||
|
elif strategy == "ignore":
|
||||||
|
return content
|
||||||
|
|
||||||
is_safestring = isinstance(content, SafeString)
|
is_safestring = isinstance(content, SafeString)
|
||||||
|
|
||||||
|
@ -1006,50 +1004,7 @@ urlpatterns = [
|
||||||
|
|
||||||
|
|
||||||
#########################################################
|
#########################################################
|
||||||
# 5. Middleware that automatically applies the dependency-
|
# 5. Template tags
|
||||||
# aggregating logic on all HTML responses.
|
|
||||||
#########################################################
|
|
||||||
|
|
||||||
|
|
||||||
@sync_and_async_middleware
|
|
||||||
class ComponentDependencyMiddleware:
|
|
||||||
"""
|
|
||||||
Middleware that inserts CSS/JS dependencies for all rendered
|
|
||||||
components at points marked with template tags.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, get_response: "Callable[[HttpRequest], HttpResponse]") -> None:
|
|
||||||
self._get_response = get_response
|
|
||||||
|
|
||||||
# NOTE: Required to work with async
|
|
||||||
if iscoroutinefunction(self._get_response):
|
|
||||||
markcoroutinefunction(self)
|
|
||||||
|
|
||||||
def __call__(self, request: HttpRequest) -> HttpResponseBase:
|
|
||||||
if iscoroutinefunction(self):
|
|
||||||
return self.__acall__(request)
|
|
||||||
|
|
||||||
response = self._get_response(request)
|
|
||||||
response = self._process_response(response)
|
|
||||||
return response
|
|
||||||
|
|
||||||
# NOTE: Required to work with async
|
|
||||||
async def __acall__(self, request: HttpRequest) -> HttpResponseBase:
|
|
||||||
response = await self._get_response(request)
|
|
||||||
response = self._process_response(response)
|
|
||||||
return response
|
|
||||||
|
|
||||||
def _process_response(self, response: HttpResponse) -> HttpResponse:
|
|
||||||
if not isinstance(response, StreamingHttpResponse) and response.get("Content-Type", "").startswith(
|
|
||||||
"text/html"
|
|
||||||
):
|
|
||||||
response.content = render_dependencies(response.content, strategy="document")
|
|
||||||
|
|
||||||
return response
|
|
||||||
|
|
||||||
|
|
||||||
#########################################################
|
|
||||||
# 6. Template tags
|
|
||||||
#########################################################
|
#########################################################
|
||||||
|
|
||||||
|
|
||||||
|
@ -1075,7 +1030,7 @@ class ComponentCssDependenciesNode(BaseNode):
|
||||||
|
|
||||||
If the generated HTML does NOT contain any `{% component_css_dependencies %}` tags, CSS links
|
If the generated HTML does NOT contain any `{% component_css_dependencies %}` tags, CSS links
|
||||||
are by default inserted into the `<head>` tag of the HTML. (See
|
are by default inserted into the `<head>` tag of the HTML. (See
|
||||||
[JS and CSS output locations](../../concepts/advanced/rendering_js_css/#js-and-css-output-locations))
|
[Default JS / CSS locations](../../concepts/advanced/rendering_js_css/#default-js-css-locations))
|
||||||
|
|
||||||
Note that there should be only one `{% component_css_dependencies %}` for the whole HTML document.
|
Note that there should be only one `{% component_css_dependencies %}` for the whole HTML document.
|
||||||
If you insert this tag multiple times, ALL CSS links will be duplicately inserted into ALL these places.
|
If you insert this tag multiple times, ALL CSS links will be duplicately inserted into ALL these places.
|
||||||
|
@ -1097,7 +1052,7 @@ class ComponentJsDependenciesNode(BaseNode):
|
||||||
|
|
||||||
If the generated HTML does NOT contain any `{% component_js_dependencies %}` tags, JS scripts
|
If the generated HTML does NOT contain any `{% component_js_dependencies %}` tags, JS scripts
|
||||||
are by default inserted at the end of the `<body>` tag of the HTML. (See
|
are by default inserted at the end of the `<body>` tag of the HTML. (See
|
||||||
[JS and CSS output locations](../../concepts/advanced/rendering_js_css/#js-and-css-output-locations))
|
[Default JS / CSS locations](../../concepts/advanced/rendering_js_css/#default-js-css-locations))
|
||||||
|
|
||||||
Note that there should be only one `{% component_js_dependencies %}` for the whole HTML document.
|
Note that there should be only one `{% component_js_dependencies %}` for the whole HTML document.
|
||||||
If you insert this tag multiple times, ALL JS scripts will be duplicately inserted into ALL these places.
|
If you insert this tag multiple times, ALL JS scripts will be duplicately inserted into ALL these places.
|
||||||
|
|
|
@ -1,4 +0,0 @@
|
||||||
# These middlewares are part of public API
|
|
||||||
from django_components.dependencies import ComponentDependencyMiddleware
|
|
||||||
|
|
||||||
__all__ = ["ComponentDependencyMiddleware"]
|
|
|
@ -1102,7 +1102,11 @@ def _nodelist_to_slot_render_func(
|
||||||
|
|
||||||
trace_component_msg("RENDER_NODELIST", component_name, component_id=None, slot_name=slot_name)
|
trace_component_msg("RENDER_NODELIST", component_name, component_id=None, slot_name=slot_name)
|
||||||
|
|
||||||
rendered = template.render(ctx)
|
# 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 ctx.push({"DJC_DEPS_STRATEGY": "ignore"}):
|
||||||
|
rendered = template.render(ctx)
|
||||||
|
|
||||||
# After the rendering is done, remove the `extra_context` from the context stack
|
# After the rendering is done, remove the `extra_context` from the context stack
|
||||||
ctx.dicts.pop(index_of_last_component_layer)
|
ctx.dicts.pop(index_of_last_component_layer)
|
||||||
|
|
|
@ -3,6 +3,8 @@ from typing import Any, Type
|
||||||
from django.template import Context, NodeList, Template
|
from django.template import Context, NodeList, Template
|
||||||
from django.template.base import Parser
|
from django.template.base import Parser
|
||||||
|
|
||||||
|
from django_components.context import _COMPONENT_CONTEXT_KEY, _STRATEGY_CONTEXT_KEY
|
||||||
|
from django_components.dependencies import COMPONENT_COMMENT_REGEX, render_dependencies
|
||||||
from django_components.util.template_parser import parse_template
|
from django_components.util.template_parser import parse_template
|
||||||
|
|
||||||
|
|
||||||
|
@ -81,11 +83,9 @@ def monkeypatch_template_render(template_cls: Type[Template]) -> None:
|
||||||
# Do not patch if done so already. This helps us avoid RecursionError
|
# Do not patch if done so already. This helps us avoid RecursionError
|
||||||
return
|
return
|
||||||
|
|
||||||
|
# NOTE: This implementation is based on Django v5.1.3)
|
||||||
def _template_render(self: Template, context: Context, *args: Any, **kwargs: Any) -> str:
|
def _template_render(self: Template, context: Context, *args: Any, **kwargs: Any) -> str:
|
||||||
"Display stage -- can be called many times"
|
"Display stage -- can be called many times"
|
||||||
# ---------------- ORIGINAL (Django v5.1.3) ----------------
|
|
||||||
# with context.render_context.push_state(self):
|
|
||||||
# ---------------- OUR CHANGES START ----------------
|
|
||||||
# We parametrized `isolated_context`, which was `True` in the original method.
|
# We parametrized `isolated_context`, which was `True` in the original method.
|
||||||
if not hasattr(self, "_djc_is_component_nested"):
|
if not hasattr(self, "_djc_is_component_nested"):
|
||||||
isolated_context = True
|
isolated_context = True
|
||||||
|
@ -95,13 +95,44 @@ def monkeypatch_template_render(template_cls: Type[Template]) -> None:
|
||||||
isolated_context = not self._djc_is_component_nested
|
isolated_context = not self._djc_is_component_nested
|
||||||
|
|
||||||
with context.render_context.push_state(self, isolated_context=isolated_context):
|
with context.render_context.push_state(self, isolated_context=isolated_context):
|
||||||
# ---------------- OUR CHANGES END ----------------
|
|
||||||
if context.template is None:
|
if context.template is None:
|
||||||
with context.bind_template(self):
|
with context.bind_template(self):
|
||||||
context.template_name = self.name
|
context.template_name = self.name
|
||||||
return self._render(context, *args, **kwargs)
|
result: str = self._render(context, *args, **kwargs)
|
||||||
else:
|
else:
|
||||||
return self._render(context, *args, **kwargs)
|
result = self._render(context, *args, **kwargs)
|
||||||
|
|
||||||
|
# If the key is present, that means this Template is rendered as part of `Component.render()`
|
||||||
|
# or `{% component %}`. In that case the parent component will take care of rendering the
|
||||||
|
# dependencies, so we don't need to do that here.
|
||||||
|
if _COMPONENT_CONTEXT_KEY in context:
|
||||||
|
return result
|
||||||
|
|
||||||
|
# NOTE: Only process dependencies if the rendered result contains AT LEAST ONE rendered component.
|
||||||
|
# This has two reasons:
|
||||||
|
# 1. To keep the behavior consistent with the previous implementation, when `Template.render()`
|
||||||
|
# didn't call `render_dependencies()`.
|
||||||
|
# 2. To avoid unnecessary processing which otherwise has a considerable perf overhead.
|
||||||
|
# See https://github.com/django-components/django-components/pull/1166#issuecomment-2850899765
|
||||||
|
if not COMPONENT_COMMENT_REGEX.search(result.encode("utf-8")):
|
||||||
|
return result
|
||||||
|
|
||||||
|
# Allow users to configure the `deps_strategy` kwarg of `render_dependencies()`, even if
|
||||||
|
# they render a Template directly with `Template.render()` or Django's `django.shortcuts.render()`.
|
||||||
|
#
|
||||||
|
# Example:
|
||||||
|
# ```
|
||||||
|
# result = render_dependencies(
|
||||||
|
# result,
|
||||||
|
# Context({ "DJC_DEPS_STRATEGY": "fragment" }),
|
||||||
|
# )
|
||||||
|
# ```
|
||||||
|
if _STRATEGY_CONTEXT_KEY in context and context[_STRATEGY_CONTEXT_KEY] is not None:
|
||||||
|
strategy = context[_STRATEGY_CONTEXT_KEY]
|
||||||
|
result = render_dependencies(result, strategy)
|
||||||
|
else:
|
||||||
|
result = render_dependencies(result)
|
||||||
|
return result
|
||||||
|
|
||||||
template_cls.render = _template_render
|
template_cls.render = _template_render
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@ from django.http import HttpResponse
|
||||||
from django.template import Context, Template
|
from django.template import Context, Template
|
||||||
from testserver.components import FragComp, FragMedia
|
from testserver.components import FragComp, FragMedia
|
||||||
|
|
||||||
from django_components import render_dependencies, types
|
from django_components import types
|
||||||
|
|
||||||
|
|
||||||
def single_component_view(request):
|
def single_component_view(request):
|
||||||
|
@ -23,8 +23,7 @@ def single_component_view(request):
|
||||||
"""
|
"""
|
||||||
template = Template(template_str)
|
template = Template(template_str)
|
||||||
|
|
||||||
rendered_raw = template.render(Context({}))
|
rendered = template.render(Context({}))
|
||||||
rendered = render_dependencies(rendered_raw)
|
|
||||||
return HttpResponse(rendered)
|
return HttpResponse(rendered)
|
||||||
|
|
||||||
|
|
||||||
|
@ -47,8 +46,7 @@ def multiple_components_view(request):
|
||||||
</html>
|
</html>
|
||||||
"""
|
"""
|
||||||
template = Template(template_str)
|
template = Template(template_str)
|
||||||
rendered_raw = template.render(Context({}))
|
rendered = template.render(Context({}))
|
||||||
rendered = render_dependencies(rendered_raw)
|
|
||||||
return HttpResponse(rendered)
|
return HttpResponse(rendered)
|
||||||
|
|
||||||
|
|
||||||
|
@ -72,8 +70,7 @@ def check_js_order_in_js_view(request):
|
||||||
</html>
|
</html>
|
||||||
"""
|
"""
|
||||||
template = Template(template_str)
|
template = Template(template_str)
|
||||||
rendered_raw = template.render(Context({}))
|
rendered = template.render(Context({}))
|
||||||
rendered = render_dependencies(rendered_raw)
|
|
||||||
return HttpResponse(rendered)
|
return HttpResponse(rendered)
|
||||||
|
|
||||||
|
|
||||||
|
@ -97,8 +94,7 @@ def check_js_order_in_media_view(request):
|
||||||
</html>
|
</html>
|
||||||
"""
|
"""
|
||||||
template = Template(template_str)
|
template = Template(template_str)
|
||||||
rendered_raw = template.render(Context({}))
|
rendered = template.render(Context({}))
|
||||||
rendered = render_dependencies(rendered_raw)
|
|
||||||
return HttpResponse(rendered)
|
return HttpResponse(rendered)
|
||||||
|
|
||||||
|
|
||||||
|
@ -122,8 +118,7 @@ def check_js_order_vars_not_available_before_view(request):
|
||||||
</html>
|
</html>
|
||||||
"""
|
"""
|
||||||
template = Template(template_str)
|
template = Template(template_str)
|
||||||
rendered_raw = template.render(Context({}))
|
rendered = template.render(Context({}))
|
||||||
rendered = render_dependencies(rendered_raw)
|
|
||||||
return HttpResponse(rendered)
|
return HttpResponse(rendered)
|
||||||
|
|
||||||
|
|
||||||
|
@ -163,14 +158,13 @@ def fragment_base_js_view(request):
|
||||||
template = Template(template_str)
|
template = Template(template_str)
|
||||||
|
|
||||||
frag = request.GET["frag"]
|
frag = request.GET["frag"]
|
||||||
rendered_raw = template.render(
|
rendered = template.render(
|
||||||
Context(
|
Context(
|
||||||
{
|
{
|
||||||
"frag": frag,
|
"frag": frag,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
rendered = render_dependencies(rendered_raw)
|
|
||||||
return HttpResponse(rendered)
|
return HttpResponse(rendered)
|
||||||
|
|
||||||
|
|
||||||
|
@ -211,8 +205,7 @@ def fragment_base_alpine_view(request):
|
||||||
template = Template(template_str)
|
template = Template(template_str)
|
||||||
|
|
||||||
frag = request.GET["frag"]
|
frag = request.GET["frag"]
|
||||||
rendered_raw = template.render(Context({"frag": frag}))
|
rendered = template.render(Context({"frag": frag}))
|
||||||
rendered = render_dependencies(rendered_raw)
|
|
||||||
return HttpResponse(rendered)
|
return HttpResponse(rendered)
|
||||||
|
|
||||||
|
|
||||||
|
@ -242,8 +235,7 @@ def fragment_base_htmx_view(request):
|
||||||
template = Template(template_str)
|
template = Template(template_str)
|
||||||
|
|
||||||
frag = request.GET["frag"]
|
frag = request.GET["frag"]
|
||||||
rendered_raw = template.render(Context({"frag": frag}))
|
rendered = template.render(Context({"frag": frag}))
|
||||||
rendered = render_dependencies(rendered_raw)
|
|
||||||
return HttpResponse(rendered)
|
return HttpResponse(rendered)
|
||||||
|
|
||||||
|
|
||||||
|
@ -273,8 +265,7 @@ def alpine_in_head_view(request):
|
||||||
</html>
|
</html>
|
||||||
"""
|
"""
|
||||||
template = Template(template_str)
|
template = Template(template_str)
|
||||||
rendered_raw = template.render(Context({}))
|
rendered = template.render(Context({}))
|
||||||
rendered = render_dependencies(rendered_raw)
|
|
||||||
return HttpResponse(rendered)
|
return HttpResponse(rendered)
|
||||||
|
|
||||||
|
|
||||||
|
@ -294,8 +285,7 @@ def alpine_in_body_view(request):
|
||||||
</html>
|
</html>
|
||||||
"""
|
"""
|
||||||
template = Template(template_str)
|
template = Template(template_str)
|
||||||
rendered_raw = template.render(Context({}))
|
rendered = template.render(Context({}))
|
||||||
rendered = render_dependencies(rendered_raw)
|
|
||||||
return HttpResponse(rendered)
|
return HttpResponse(rendered)
|
||||||
|
|
||||||
|
|
||||||
|
@ -316,8 +306,7 @@ def alpine_in_body_view_2(request):
|
||||||
</html>
|
</html>
|
||||||
"""
|
"""
|
||||||
template = Template(template_str)
|
template = Template(template_str)
|
||||||
rendered_raw = template.render(Context({}))
|
rendered = template.render(Context({}))
|
||||||
rendered = render_dependencies(rendered_raw)
|
|
||||||
return HttpResponse(rendered)
|
return HttpResponse(rendered)
|
||||||
|
|
||||||
|
|
||||||
|
@ -338,6 +327,5 @@ def alpine_in_body_vars_not_available_before_view(request):
|
||||||
</html>
|
</html>
|
||||||
"""
|
"""
|
||||||
template = Template(template_str)
|
template = Template(template_str)
|
||||||
rendered_raw = template.render(Context({}))
|
rendered = template.render(Context({}))
|
||||||
rendered = render_dependencies(rendered_raw)
|
|
||||||
return HttpResponse(rendered)
|
return HttpResponse(rendered)
|
||||||
|
|
|
@ -70,7 +70,7 @@ if not settings.configured:
|
||||||
"autodiscover": False,
|
"autodiscover": False,
|
||||||
"context_behavior": CONTEXT_MODE,
|
"context_behavior": CONTEXT_MODE,
|
||||||
},
|
},
|
||||||
MIDDLEWARE=["django_components.middleware.ComponentDependencyMiddleware"],
|
MIDDLEWARE=[],
|
||||||
DATABASES={
|
DATABASES={
|
||||||
"default": {
|
"default": {
|
||||||
"ENGINE": "django.db.backends.sqlite3",
|
"ENGINE": "django.db.backends.sqlite3",
|
||||||
|
|
|
@ -41,7 +41,7 @@ if not settings.configured:
|
||||||
"autodiscover": False,
|
"autodiscover": False,
|
||||||
"context_behavior": CONTEXT_MODE,
|
"context_behavior": CONTEXT_MODE,
|
||||||
},
|
},
|
||||||
MIDDLEWARE=["django_components.middleware.ComponentDependencyMiddleware"],
|
MIDDLEWARE=[],
|
||||||
DATABASES={
|
DATABASES={
|
||||||
"default": {
|
"default": {
|
||||||
"ENGINE": "django.db.backends.sqlite3",
|
"ENGINE": "django.db.backends.sqlite3",
|
||||||
|
|
|
@ -70,7 +70,7 @@ if not settings.configured:
|
||||||
"autodiscover": False,
|
"autodiscover": False,
|
||||||
"context_behavior": CONTEXT_MODE,
|
"context_behavior": CONTEXT_MODE,
|
||||||
},
|
},
|
||||||
MIDDLEWARE=["django_components.middleware.ComponentDependencyMiddleware"],
|
MIDDLEWARE=[],
|
||||||
DATABASES={
|
DATABASES={
|
||||||
"default": {
|
"default": {
|
||||||
"ENGINE": "django.db.backends.sqlite3",
|
"ENGINE": "django.db.backends.sqlite3",
|
||||||
|
@ -2455,6 +2455,7 @@ class ProjectLayoutTabbed(Component):
|
||||||
"stroke_width": 2,
|
"stroke_width": 2,
|
||||||
"color": "text-gray-400 hover:text-gray-500",
|
"color": "text-gray-400 hover:text-gray-500",
|
||||||
},
|
},
|
||||||
|
# deps_strategy="ignore",
|
||||||
render_dependencies=False,
|
render_dependencies=False,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -4490,6 +4491,7 @@ class Tabs(Component):
|
||||||
"header_attrs": context["header_attrs"],
|
"header_attrs": context["header_attrs"],
|
||||||
"content_attrs": context["content_attrs"],
|
"content_attrs": context["content_attrs"],
|
||||||
},
|
},
|
||||||
|
# deps_strategy="ignore",
|
||||||
render_dependencies=False,
|
render_dependencies=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -5135,6 +5137,7 @@ class ProjectUsers(Component):
|
||||||
"project_id": project_id,
|
"project_id": project_id,
|
||||||
"role_id": role['id'],
|
"role_id": role['id'],
|
||||||
},
|
},
|
||||||
|
# deps_strategy="ignore",
|
||||||
render_dependencies=False,
|
render_dependencies=False,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -41,7 +41,7 @@ if not settings.configured:
|
||||||
"autodiscover": False,
|
"autodiscover": False,
|
||||||
"context_behavior": CONTEXT_MODE,
|
"context_behavior": CONTEXT_MODE,
|
||||||
},
|
},
|
||||||
MIDDLEWARE=["django_components.middleware.ComponentDependencyMiddleware"],
|
MIDDLEWARE=[],
|
||||||
DATABASES={
|
DATABASES={
|
||||||
"default": {
|
"default": {
|
||||||
"ENGINE": "django.db.backends.sqlite3",
|
"ENGINE": "django.db.backends.sqlite3",
|
||||||
|
|
|
@ -872,7 +872,7 @@ class TestComponentRender:
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
"""
|
"""
|
||||||
|
|
||||||
rendered = SimpleComponent.render(render_dependencies=False)
|
rendered = SimpleComponent.render(deps_strategy="ignore")
|
||||||
assertHTMLEqual(
|
assertHTMLEqual(
|
||||||
rendered,
|
rendered,
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -79,7 +79,7 @@ class TestMainMedia:
|
||||||
assert ".html-css-only {\n color: blue;\n}" in TestComponent.css # type: ignore[operator]
|
assert ".html-css-only {\n color: blue;\n}" in TestComponent.css # type: ignore[operator]
|
||||||
assert 'console.log("JS file");' in TestComponent.js # type: ignore[operator]
|
assert 'console.log("JS file");' in TestComponent.js # type: ignore[operator]
|
||||||
|
|
||||||
rendered_raw = Template(
|
rendered = Template(
|
||||||
"""
|
"""
|
||||||
{% load component_tags %}
|
{% load component_tags %}
|
||||||
{% component_js_dependencies %}
|
{% component_js_dependencies %}
|
||||||
|
@ -87,7 +87,6 @@ class TestMainMedia:
|
||||||
{% component "test" variable="test" / %}
|
{% component "test" variable="test" / %}
|
||||||
"""
|
"""
|
||||||
).render(Context())
|
).render(Context())
|
||||||
rendered = render_dependencies(rendered_raw)
|
|
||||||
|
|
||||||
assertInHTML(
|
assertInHTML(
|
||||||
"""
|
"""
|
||||||
|
@ -143,7 +142,7 @@ class TestMainMedia:
|
||||||
assert ".html-css-only {\n color: blue;\n}" in TestComponent.css # type: ignore[operator]
|
assert ".html-css-only {\n color: blue;\n}" in TestComponent.css # type: ignore[operator]
|
||||||
assert 'console.log("HTML and JS only");' in TestComponent.js # type: ignore[operator]
|
assert 'console.log("HTML and JS only");' in TestComponent.js # type: ignore[operator]
|
||||||
|
|
||||||
rendered_raw = Template(
|
rendered = Template(
|
||||||
"""
|
"""
|
||||||
{% load component_tags %}
|
{% load component_tags %}
|
||||||
{% component_js_dependencies %}
|
{% component_js_dependencies %}
|
||||||
|
@ -151,7 +150,6 @@ class TestMainMedia:
|
||||||
{% component "test" variable="test" / %}
|
{% component "test" variable="test" / %}
|
||||||
"""
|
"""
|
||||||
).render(Context())
|
).render(Context())
|
||||||
rendered = render_dependencies(rendered_raw)
|
|
||||||
|
|
||||||
assert 'Variable: <strong data-djc-id-ca1bc41="">test</strong>' in rendered
|
assert 'Variable: <strong data-djc-id-ca1bc41="">test</strong>' in rendered
|
||||||
assertInHTML(
|
assertInHTML(
|
||||||
|
|
|
@ -6,19 +6,15 @@ For checking the OUTPUT of the dependencies, see `test_dependency_rendering.py`.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import re
|
import re
|
||||||
from unittest.mock import Mock
|
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from django.http import HttpResponseNotModified
|
|
||||||
from django.template import Context, Template
|
from django.template import Context, Template
|
||||||
from pytest_django.asserts import assertHTMLEqual, assertInHTML
|
from pytest_django.asserts import assertHTMLEqual, assertInHTML
|
||||||
|
|
||||||
from django_components import Component, registry, render_dependencies, types
|
from django_components import Component, registry, render_dependencies, types
|
||||||
from django_components.components.dynamic import DynamicComponent
|
|
||||||
from django_components.middleware import ComponentDependencyMiddleware
|
|
||||||
|
|
||||||
from django_components.testing import djc_test
|
from django_components.testing import djc_test
|
||||||
from .testutils import create_and_process_template_response, setup_test_config
|
from .testutils import setup_test_config
|
||||||
|
|
||||||
setup_test_config({"autodiscover": False})
|
setup_test_config({"autodiscover": False})
|
||||||
|
|
||||||
|
@ -69,7 +65,8 @@ class TestDependenciesLegacy:
|
||||||
|
|
||||||
@djc_test
|
@djc_test
|
||||||
class TestRenderDependencies:
|
class TestRenderDependencies:
|
||||||
def test_standalone_render_dependencies(self):
|
# Check that `render_dependencies()` works when called directly
|
||||||
|
def test_render_dependencies(self):
|
||||||
registry.register(name="test", component=SimpleComponent)
|
registry.register(name="test", component=SimpleComponent)
|
||||||
|
|
||||||
template_str: types.django_html = """
|
template_str: types.django_html = """
|
||||||
|
@ -79,7 +76,8 @@ class TestRenderDependencies:
|
||||||
{% component 'test' variable='foo' / %}
|
{% component 'test' variable='foo' / %}
|
||||||
"""
|
"""
|
||||||
template = Template(template_str)
|
template = Template(template_str)
|
||||||
rendered_raw: str = template.render(Context({}))
|
# NOTE: `"ignore"` is a special value that means "do not render dependencies"
|
||||||
|
rendered_raw: str = template.render(Context({"DJC_DEPS_STRATEGY": "ignore"}))
|
||||||
|
|
||||||
# Placeholders
|
# Placeholders
|
||||||
assert rendered_raw.count('<link name="CSS_PLACEHOLDER">') == 1
|
assert rendered_raw.count('<link name="CSS_PLACEHOLDER">') == 1
|
||||||
|
@ -100,7 +98,8 @@ class TestRenderDependencies:
|
||||||
|
|
||||||
assertInHTML('<link href="style.css" media="all" rel="stylesheet">', rendered, count=1) # Media.css
|
assertInHTML('<link href="style.css" media="all" rel="stylesheet">', rendered, count=1) # Media.css
|
||||||
|
|
||||||
def test_middleware_renders_dependencies(self):
|
# Check that instead of `render_dependencies()`, we can simply call `Template.render()`
|
||||||
|
def test_template_render(self):
|
||||||
registry.register(name="test", component=SimpleComponent)
|
registry.register(name="test", component=SimpleComponent)
|
||||||
|
|
||||||
template_str: types.django_html = """
|
template_str: types.django_html = """
|
||||||
|
@ -110,19 +109,64 @@ class TestRenderDependencies:
|
||||||
{% component 'test' variable='foo' / %}
|
{% component 'test' variable='foo' / %}
|
||||||
"""
|
"""
|
||||||
template = Template(template_str)
|
template = Template(template_str)
|
||||||
rendered = create_and_process_template_response(template, use_middleware=True)
|
rendered = template.render(Context({}))
|
||||||
|
|
||||||
# Dependency manager script
|
# Dependency manager script
|
||||||
assertInHTML('<script src="django_components/django_components.min.js"></script>', rendered, count=1)
|
assertInHTML('<script src="django_components/django_components.min.js"></script>', rendered, count=1)
|
||||||
|
|
||||||
assertInHTML("<style>.xyz { color: red; }</style>", rendered, count=1) # Inlined CSS
|
assertInHTML(
|
||||||
assertInHTML('<script>console.log("xyz");</script>', rendered, count=1) # Inlined JS
|
"<style>.xyz { color: red; }</style>",
|
||||||
|
rendered,
|
||||||
|
count=1,
|
||||||
|
) # Inlined CSS
|
||||||
|
assertInHTML(
|
||||||
|
'<script>console.log("xyz");</script>', rendered, count=1
|
||||||
|
) # Inlined JS
|
||||||
|
|
||||||
assertInHTML('<link href="style.css" media="all" rel="stylesheet">', rendered, count=1) # Media.css
|
assertInHTML('<link href="style.css" media="all" rel="stylesheet">', rendered, count=1) # Media.css
|
||||||
assert rendered.count("<link") == 1
|
assert rendered.count("<link") == 1
|
||||||
assert rendered.count("<style") == 1
|
assert rendered.count("<style") == 1
|
||||||
|
|
||||||
def test_component_render_renders_dependencies(self):
|
# Check that we can change the dependencies strategy via `DJC_DEPS_STRATEGY` context key
|
||||||
|
def test_template_render_deps_strategy(self):
|
||||||
|
registry.register(name="test", component=SimpleComponent)
|
||||||
|
|
||||||
|
template_str: types.django_html = """
|
||||||
|
{% load component_tags %}
|
||||||
|
{% component_js_dependencies %}
|
||||||
|
{% component_css_dependencies %}
|
||||||
|
{% component 'test' variable='foo' / %}
|
||||||
|
"""
|
||||||
|
template = Template(template_str)
|
||||||
|
rendered: str = template.render(Context({"DJC_DEPS_STRATEGY": "append"}))
|
||||||
|
|
||||||
|
# Dependency manager script NOT included
|
||||||
|
assertInHTML('<script src="django_components/django_components.min.js"></script>', rendered, count=0)
|
||||||
|
|
||||||
|
assertInHTML(
|
||||||
|
"<style>.xyz { color: red; }</style>",
|
||||||
|
rendered,
|
||||||
|
count=1,
|
||||||
|
) # Inlined CSS
|
||||||
|
assertInHTML(
|
||||||
|
'<script>console.log("xyz");</script>', rendered, count=1
|
||||||
|
) # Inlined JS
|
||||||
|
|
||||||
|
assertInHTML('<link href="style.css" media="all" rel="stylesheet">', rendered, count=1) # Media.css
|
||||||
|
assert rendered.count("<link") == 1
|
||||||
|
assert rendered.count("<style") == 1
|
||||||
|
|
||||||
|
# Check that the order is correct (dependencies are appended)
|
||||||
|
assert rendered.strip() == (
|
||||||
|
'Variable: <strong data-djc-id-ca1bc41="">foo</strong>\n'
|
||||||
|
' \n'
|
||||||
|
' <script src="script.js"></script><script>console.log("xyz");</script><style>.xyz {\n'
|
||||||
|
' color: red;\n'
|
||||||
|
' }</style><link href="style.css" media="all" rel="stylesheet">'
|
||||||
|
)
|
||||||
|
|
||||||
|
# Check that `Component.render()` renders dependencies
|
||||||
|
def test_component_render(self):
|
||||||
class SimpleComponentWithDeps(SimpleComponent):
|
class SimpleComponentWithDeps(SimpleComponent):
|
||||||
template: types.django_html = (
|
template: types.django_html = (
|
||||||
"""
|
"""
|
||||||
|
@ -149,7 +193,7 @@ class TestRenderDependencies:
|
||||||
assert rendered.count("<link") == 1
|
assert rendered.count("<link") == 1
|
||||||
assert rendered.count("<style") == 1
|
assert rendered.count("<style") == 1
|
||||||
|
|
||||||
def test_component_render_renders_dependencies_opt_out(self):
|
def test_component_render_opt_out(self):
|
||||||
class SimpleComponentWithDeps(SimpleComponent):
|
class SimpleComponentWithDeps(SimpleComponent):
|
||||||
template: types.django_html = (
|
template: types.django_html = (
|
||||||
"""
|
"""
|
||||||
|
@ -164,7 +208,7 @@ class TestRenderDependencies:
|
||||||
|
|
||||||
rendered_raw = SimpleComponentWithDeps.render(
|
rendered_raw = SimpleComponentWithDeps.render(
|
||||||
kwargs={"variable": "foo"},
|
kwargs={"variable": "foo"},
|
||||||
render_dependencies=False,
|
deps_strategy="ignore",
|
||||||
)
|
)
|
||||||
|
|
||||||
assert rendered_raw.count("<script") == 1
|
assert rendered_raw.count("<script") == 1
|
||||||
|
@ -184,7 +228,8 @@ class TestRenderDependencies:
|
||||||
count=0,
|
count=0,
|
||||||
) # Inlined JS
|
) # Inlined JS
|
||||||
|
|
||||||
def test_component_render_to_response_renders_dependencies(self):
|
# Check that `Component.render_to_response()` renders dependencies
|
||||||
|
def test_component_render_to_response(self):
|
||||||
class SimpleComponentWithDeps(SimpleComponent):
|
class SimpleComponentWithDeps(SimpleComponent):
|
||||||
template: types.django_html = (
|
template: types.django_html = (
|
||||||
"""
|
"""
|
||||||
|
@ -212,6 +257,103 @@ class TestRenderDependencies:
|
||||||
assert rendered.count("<link") == 1
|
assert rendered.count("<link") == 1
|
||||||
assert rendered.count("<style") == 1
|
assert rendered.count("<style") == 1
|
||||||
|
|
||||||
|
def test_inserts_styles_and_script_to_default_places_if_not_overriden(self):
|
||||||
|
registry.register(name="test", component=SimpleComponent)
|
||||||
|
|
||||||
|
template_str: types.django_html = """
|
||||||
|
{% load component_tags %}
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head></head>
|
||||||
|
<body>
|
||||||
|
{% component "test" variable="foo" / %}
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
"""
|
||||||
|
rendered_raw = Template(template_str).render(Context({"DJC_DEPS_STRATEGY": "ignore"}))
|
||||||
|
rendered = render_dependencies(rendered_raw)
|
||||||
|
|
||||||
|
assert rendered.count("<script") == 4
|
||||||
|
assert rendered.count("<style") == 1
|
||||||
|
assert rendered.count("<link") == 1
|
||||||
|
assert rendered.count("_RENDERED") == 0
|
||||||
|
|
||||||
|
assertInHTML(
|
||||||
|
"""
|
||||||
|
<head>
|
||||||
|
<style>.xyz { color: red; }</style>
|
||||||
|
<link href="style.css" media="all" rel="stylesheet">
|
||||||
|
</head>
|
||||||
|
""",
|
||||||
|
rendered,
|
||||||
|
count=1,
|
||||||
|
)
|
||||||
|
|
||||||
|
body_re = re.compile(r"<body>(.*?)</body>", re.DOTALL)
|
||||||
|
rendered_body = body_re.search(rendered).group(1) # type: ignore[union-attr]
|
||||||
|
|
||||||
|
assertInHTML(
|
||||||
|
"""<script src="django_components/django_components.min.js">""",
|
||||||
|
rendered_body,
|
||||||
|
count=1,
|
||||||
|
)
|
||||||
|
assertInHTML(
|
||||||
|
'<script>console.log("xyz");</script>',
|
||||||
|
rendered_body,
|
||||||
|
count=1,
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_does_not_insert_styles_and_script_to_default_places_if_overriden(self):
|
||||||
|
registry.register(name="test", component=SimpleComponent)
|
||||||
|
|
||||||
|
template_str: types.django_html = """
|
||||||
|
{% load component_tags %}
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
{% component_js_dependencies %}
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
{% component "test" variable="foo" / %}
|
||||||
|
{% component_css_dependencies %}
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
"""
|
||||||
|
rendered_raw = Template(template_str).render(Context({"DJC_DEPS_STRATEGY": "ignore"}))
|
||||||
|
rendered: str = render_dependencies(rendered_raw)
|
||||||
|
|
||||||
|
assert rendered.count("<script") == 4
|
||||||
|
assert rendered.count("<style") == 1
|
||||||
|
assert rendered.count("<link") == 1
|
||||||
|
assert rendered.count("_RENDERED") == 0
|
||||||
|
|
||||||
|
assertInHTML(
|
||||||
|
"""
|
||||||
|
<body>
|
||||||
|
Variable: <strong data-djc-id-ca1bc41>foo</strong>
|
||||||
|
|
||||||
|
<style>.xyz { color: red; }</style>
|
||||||
|
<link href="style.css" media="all" rel="stylesheet">
|
||||||
|
</body>
|
||||||
|
""",
|
||||||
|
rendered,
|
||||||
|
count=1,
|
||||||
|
)
|
||||||
|
|
||||||
|
head_re = re.compile(r"<head>(.*?)</head>", re.DOTALL)
|
||||||
|
rendered_head = head_re.search(rendered).group(1) # type: ignore[union-attr]
|
||||||
|
|
||||||
|
assertInHTML(
|
||||||
|
"""<script src="django_components/django_components.min.js">""",
|
||||||
|
rendered_head,
|
||||||
|
count=1,
|
||||||
|
)
|
||||||
|
assertInHTML(
|
||||||
|
'<script>console.log("xyz");</script>',
|
||||||
|
rendered_head,
|
||||||
|
count=1,
|
||||||
|
)
|
||||||
|
|
||||||
# NOTE: Some HTML parser libraries like selectolax or lxml try to "correct" the given HTML.
|
# NOTE: Some HTML parser libraries like selectolax or lxml try to "correct" the given HTML.
|
||||||
# We want to avoid this behavior, so user gets the exact same HTML back.
|
# We want to avoid this behavior, so user gets the exact same HTML back.
|
||||||
def test_does_not_try_to_add_close_tags(self):
|
def test_does_not_try_to_add_close_tags(self):
|
||||||
|
@ -221,7 +363,7 @@ class TestRenderDependencies:
|
||||||
<thead>
|
<thead>
|
||||||
"""
|
"""
|
||||||
|
|
||||||
rendered_raw = Template(template_str).render(Context({"formset": [1]}))
|
rendered_raw = Template(template_str).render(Context({"formset": [1], "DJC_DEPS_STRATEGY": "ignore"}))
|
||||||
rendered = render_dependencies(rendered_raw, strategy="fragment")
|
rendered = render_dependencies(rendered_raw, strategy="fragment")
|
||||||
|
|
||||||
assertHTMLEqual(rendered, "<thead>")
|
assertHTMLEqual(rendered, "<thead>")
|
||||||
|
@ -256,7 +398,7 @@ class TestRenderDependencies:
|
||||||
</table>
|
</table>
|
||||||
"""
|
"""
|
||||||
|
|
||||||
rendered_raw = Template(template_str).render(Context({"formset": [1]}))
|
rendered_raw = Template(template_str).render(Context({"formset": [1], "DJC_DEPS_STRATEGY": "ignore"}))
|
||||||
rendered = render_dependencies(rendered_raw, strategy="fragment")
|
rendered = render_dependencies(rendered_raw, strategy="fragment")
|
||||||
|
|
||||||
expected = """
|
expected = """
|
||||||
|
@ -319,7 +461,7 @@ class TestRenderDependencies:
|
||||||
</table>
|
</table>
|
||||||
"""
|
"""
|
||||||
|
|
||||||
rendered_raw = Template(template_str).render(Context({"formset": [1]}))
|
rendered_raw = Template(template_str).render(Context({"formset": [1], "DJC_DEPS_STRATEGY": "ignore"}))
|
||||||
rendered = render_dependencies(rendered_raw, strategy="fragment")
|
rendered = render_dependencies(rendered_raw, strategy="fragment")
|
||||||
|
|
||||||
# Base64 encodings:
|
# Base64 encodings:
|
||||||
|
@ -408,7 +550,7 @@ class TestDependenciesStrategyDocument:
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
"""
|
"""
|
||||||
rendered_raw = Template(template_str).render(Context({}))
|
rendered_raw = Template(template_str).render(Context({"DJC_DEPS_STRATEGY": "ignore"}))
|
||||||
rendered = render_dependencies(rendered_raw, strategy="document")
|
rendered = render_dependencies(rendered_raw, strategy="document")
|
||||||
|
|
||||||
assert rendered.count("<script") == 4
|
assert rendered.count("<script") == 4
|
||||||
|
@ -457,7 +599,7 @@ class TestDependenciesStrategyDocument:
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
"""
|
"""
|
||||||
rendered_raw = Template(template_str).render(Context({}))
|
rendered_raw = Template(template_str).render(Context({"DJC_DEPS_STRATEGY": "ignore"}))
|
||||||
rendered = render_dependencies(rendered_raw, strategy="document")
|
rendered = render_dependencies(rendered_raw, strategy="document")
|
||||||
|
|
||||||
assert rendered.count("<script") == 4
|
assert rendered.count("<script") == 4
|
||||||
|
@ -505,7 +647,7 @@ class TestDependenciesStrategySimple:
|
||||||
{% component 'test' variable='foo' / %}
|
{% component 'test' variable='foo' / %}
|
||||||
"""
|
"""
|
||||||
template = Template(template_str)
|
template = Template(template_str)
|
||||||
rendered_raw: str = template.render(Context({}))
|
rendered_raw: str = template.render(Context({"DJC_DEPS_STRATEGY": "ignore"}))
|
||||||
|
|
||||||
# Placeholders
|
# Placeholders
|
||||||
assert rendered_raw.count('<link name="CSS_PLACEHOLDER">') == 1
|
assert rendered_raw.count('<link name="CSS_PLACEHOLDER">') == 1
|
||||||
|
@ -593,7 +735,7 @@ class TestDependenciesStrategySimple:
|
||||||
{% endcomponent %}
|
{% endcomponent %}
|
||||||
"""
|
"""
|
||||||
template = Template(template_str)
|
template = Template(template_str)
|
||||||
rendered_raw: str = template.render(Context({}))
|
rendered_raw: str = template.render(Context({"DJC_DEPS_STRATEGY": "ignore"}))
|
||||||
|
|
||||||
rendered = render_dependencies(rendered_raw, strategy="simple")
|
rendered = render_dependencies(rendered_raw, strategy="simple")
|
||||||
|
|
||||||
|
@ -663,7 +805,7 @@ class TestDependenciesStrategyPrepend:
|
||||||
{% component 'test' variable='foo' / %}
|
{% component 'test' variable='foo' / %}
|
||||||
"""
|
"""
|
||||||
template = Template(template_str)
|
template = Template(template_str)
|
||||||
rendered_raw: str = template.render(Context({}))
|
rendered_raw: str = template.render(Context({"DJC_DEPS_STRATEGY": "ignore"}))
|
||||||
|
|
||||||
# Placeholders
|
# Placeholders
|
||||||
assert rendered_raw.count('<link name="CSS_PLACEHOLDER">') == 1
|
assert rendered_raw.count('<link name="CSS_PLACEHOLDER">') == 1
|
||||||
|
@ -753,7 +895,7 @@ class TestDependenciesStrategyPrepend:
|
||||||
{% endcomponent %}
|
{% endcomponent %}
|
||||||
"""
|
"""
|
||||||
template = Template(template_str)
|
template = Template(template_str)
|
||||||
rendered_raw: str = template.render(Context({}))
|
rendered_raw: str = template.render(Context({"DJC_DEPS_STRATEGY": "ignore"}))
|
||||||
|
|
||||||
rendered = render_dependencies(rendered_raw, strategy="prepend")
|
rendered = render_dependencies(rendered_raw, strategy="prepend")
|
||||||
|
|
||||||
|
@ -823,7 +965,7 @@ class TestDependenciesStrategyAppend:
|
||||||
{% component 'test' variable='foo' / %}
|
{% component 'test' variable='foo' / %}
|
||||||
"""
|
"""
|
||||||
template = Template(template_str)
|
template = Template(template_str)
|
||||||
rendered_raw: str = template.render(Context({}))
|
rendered_raw: str = template.render(Context({"DJC_DEPS_STRATEGY": "ignore"}))
|
||||||
|
|
||||||
# Placeholders
|
# Placeholders
|
||||||
assert rendered_raw.count('<link name="CSS_PLACEHOLDER">') == 1
|
assert rendered_raw.count('<link name="CSS_PLACEHOLDER">') == 1
|
||||||
|
@ -910,7 +1052,7 @@ class TestDependenciesStrategyAppend:
|
||||||
{% endcomponent %}
|
{% endcomponent %}
|
||||||
"""
|
"""
|
||||||
template = Template(template_str)
|
template = Template(template_str)
|
||||||
rendered_raw: str = template.render(Context({}))
|
rendered_raw: str = template.render(Context({"DJC_DEPS_STRATEGY": "ignore"}))
|
||||||
|
|
||||||
rendered = render_dependencies(rendered_raw, strategy="append")
|
rendered = render_dependencies(rendered_raw, strategy="append")
|
||||||
|
|
||||||
|
@ -969,58 +1111,32 @@ class TestDependenciesStrategyAppend:
|
||||||
|
|
||||||
|
|
||||||
@djc_test
|
@djc_test
|
||||||
class TestMiddleware:
|
class TestDependenciesStrategyRaw:
|
||||||
def test_middleware_response_without_content_type(self):
|
def test_single_component(self):
|
||||||
response = HttpResponseNotModified()
|
registry.register(name="test", component=SimpleComponent)
|
||||||
middleware = ComponentDependencyMiddleware(get_response=lambda _: response)
|
|
||||||
request = Mock()
|
|
||||||
assert response == middleware(request=request)
|
|
||||||
|
|
||||||
def test_middleware_response_with_components_with_slash_dash_and_underscore(self):
|
|
||||||
registry.register("dynamic", DynamicComponent)
|
|
||||||
registry.register("test-component", component=SimpleComponent)
|
|
||||||
registry.register("test/component", component=SimpleComponent)
|
|
||||||
registry.register("test_component", component=SimpleComponent)
|
|
||||||
|
|
||||||
template_str: types.django_html = """
|
template_str: types.django_html = """
|
||||||
{% load component_tags %}
|
{% load component_tags %}
|
||||||
{% component_css_dependencies %}
|
|
||||||
{% component_js_dependencies %}
|
{% component_js_dependencies %}
|
||||||
{% component "dynamic" is=component_name variable='value' / %}
|
{% component_css_dependencies %}
|
||||||
|
{% component 'test' variable='foo' / %}
|
||||||
"""
|
"""
|
||||||
template = Template(template_str)
|
template = Template(template_str)
|
||||||
|
rendered_raw: str = template.render(Context({"DJC_DEPS_STRATEGY": "ignore"}))
|
||||||
|
|
||||||
def assert_dependencies(content: str):
|
# Placeholders
|
||||||
# Dependency manager script (empty)
|
assert rendered_raw.count('<link name="CSS_PLACEHOLDER">') == 1
|
||||||
assertInHTML('<script src="django_components/django_components.min.js"></script>', content, count=1)
|
assert rendered_raw.count('<script name="JS_PLACEHOLDER"></script>') == 1
|
||||||
|
|
||||||
# Inlined JS
|
assert rendered_raw.count("<script") == 1
|
||||||
assertInHTML('<script>console.log("xyz");</script>', content, count=1)
|
assert rendered_raw.count("<style") == 0
|
||||||
# Inlined CSS
|
assert rendered_raw.count("<link") == 1
|
||||||
assertInHTML("<style>.xyz { color: red; }</style>", content, count=1)
|
assert rendered_raw.count("_RENDERED") == 1
|
||||||
# Media.css
|
|
||||||
assertInHTML('<link href="style.css" media="all" rel="stylesheet">', content, count=1)
|
|
||||||
|
|
||||||
rendered1 = create_and_process_template_response(
|
# Check that it contains inlined JS and CSS, and Media.css
|
||||||
template,
|
assert rendered_raw.strip() == (
|
||||||
context=Context({"component_name": "test-component"}),
|
'<script name="JS_PLACEHOLDER"></script>\n'
|
||||||
|
' <link name="CSS_PLACEHOLDER">\n'
|
||||||
|
' <!-- _RENDERED SimpleComponent_311097,ca1bc41,, -->\n'
|
||||||
|
' Variable: <strong data-djc-id-ca1bc41="">foo</strong>'
|
||||||
)
|
)
|
||||||
|
|
||||||
assert_dependencies(rendered1)
|
|
||||||
assert rendered1.count('Variable: <strong data-djc-id-ca1bc42="" data-djc-id-ca1bc41="">value</strong>') == 1
|
|
||||||
|
|
||||||
rendered2 = create_and_process_template_response(
|
|
||||||
template,
|
|
||||||
context=Context({"component_name": "test-component"}),
|
|
||||||
)
|
|
||||||
|
|
||||||
assert_dependencies(rendered2)
|
|
||||||
assert rendered2.count('Variable: <strong data-djc-id-ca1bc44="" data-djc-id-ca1bc43="">value</strong>') == 1
|
|
||||||
|
|
||||||
rendered3 = create_and_process_template_response(
|
|
||||||
template,
|
|
||||||
context=Context({"component_name": "test_component"}),
|
|
||||||
)
|
|
||||||
|
|
||||||
assert_dependencies(rendered3)
|
|
||||||
assert rendered3.count('Variable: <strong data-djc-id-ca1bc46="" data-djc-id-ca1bc45="">value</strong>') == 1
|
|
||||||
|
|
|
@ -11,7 +11,7 @@ from pytest_django.asserts import assertHTMLEqual, assertInHTML
|
||||||
from django_components import Component, registry, types
|
from django_components import Component, registry, types
|
||||||
from django_components.testing import djc_test
|
from django_components.testing import djc_test
|
||||||
|
|
||||||
from .testutils import create_and_process_template_response, setup_test_config
|
from .testutils import setup_test_config
|
||||||
|
|
||||||
setup_test_config({"autodiscover": False})
|
setup_test_config({"autodiscover": False})
|
||||||
|
|
||||||
|
@ -120,13 +120,13 @@ class TestDependencyRendering:
|
||||||
{% component_css_dependencies %}
|
{% component_css_dependencies %}
|
||||||
"""
|
"""
|
||||||
template = Template(template_str)
|
template = Template(template_str)
|
||||||
rendered = create_and_process_template_response(template)
|
rendered: str = template.render(Context({}))
|
||||||
|
|
||||||
# Dependency manager script
|
# Dependency manager script
|
||||||
assertInHTML('<script src="django_components/django_components.min.js"></script>', rendered, count=1)
|
assertInHTML('<script src="django_components/django_components.min.js"></script>', rendered, count=0)
|
||||||
|
|
||||||
assert rendered.count("<script") == 1 # 1 boilerplate script
|
assert rendered.count("<script") == 1 # 1 placeholder script
|
||||||
assert rendered.count("<link") == 0 # No CSS
|
assert rendered.count("<link") == 1 # 1 placeholder link
|
||||||
assert rendered.count("<style") == 0
|
assert rendered.count("<style") == 0
|
||||||
|
|
||||||
assert "loadedJsUrls" not in rendered
|
assert "loadedJsUrls" not in rendered
|
||||||
|
@ -134,6 +134,11 @@ class TestDependencyRendering:
|
||||||
assert "toLoadJsTags" not in rendered
|
assert "toLoadJsTags" not in rendered
|
||||||
assert "toLoadCssTags" not in rendered
|
assert "toLoadCssTags" not in rendered
|
||||||
|
|
||||||
|
assert rendered.strip() == (
|
||||||
|
'<script name="JS_PLACEHOLDER"></script>\n'
|
||||||
|
' <link name="CSS_PLACEHOLDER">'
|
||||||
|
)
|
||||||
|
|
||||||
def test_no_js_dependencies_when_no_components_used(self):
|
def test_no_js_dependencies_when_no_components_used(self):
|
||||||
registry.register(name="test", component=SimpleComponent)
|
registry.register(name="test", component=SimpleComponent)
|
||||||
|
|
||||||
|
@ -141,12 +146,12 @@ class TestDependencyRendering:
|
||||||
{% load component_tags %}{% component_js_dependencies %}
|
{% load component_tags %}{% component_js_dependencies %}
|
||||||
"""
|
"""
|
||||||
template = Template(template_str)
|
template = Template(template_str)
|
||||||
rendered = create_and_process_template_response(template)
|
rendered = template.render(Context({}))
|
||||||
|
|
||||||
# Dependency manager script
|
# Dependency manager script
|
||||||
assertInHTML('<script src="django_components/django_components.min.js"></script>', rendered, count=1)
|
assertInHTML('<script src="django_components/django_components.min.js"></script>', rendered, count=0)
|
||||||
|
|
||||||
assert rendered.count("<script") == 1 # 1 boilerplate script
|
assert rendered.count("<script") == 1 # 1 placeholder script
|
||||||
assert rendered.count("<link") == 0 # No CSS
|
assert rendered.count("<link") == 0 # No CSS
|
||||||
assert rendered.count("<style") == 0
|
assert rendered.count("<style") == 0
|
||||||
|
|
||||||
|
@ -155,6 +160,8 @@ class TestDependencyRendering:
|
||||||
assert "toLoadJsTags" not in rendered
|
assert "toLoadJsTags" not in rendered
|
||||||
assert "toLoadCssTags" not in rendered
|
assert "toLoadCssTags" not in rendered
|
||||||
|
|
||||||
|
assert rendered.strip() == '<script name="JS_PLACEHOLDER"></script>'
|
||||||
|
|
||||||
def test_no_css_dependencies_when_no_components_used(self):
|
def test_no_css_dependencies_when_no_components_used(self):
|
||||||
registry.register(name="test", component=SimpleComponent)
|
registry.register(name="test", component=SimpleComponent)
|
||||||
|
|
||||||
|
@ -162,12 +169,14 @@ class TestDependencyRendering:
|
||||||
{% load component_tags %}{% component_css_dependencies %}
|
{% load component_tags %}{% component_css_dependencies %}
|
||||||
"""
|
"""
|
||||||
template = Template(template_str)
|
template = Template(template_str)
|
||||||
rendered = create_and_process_template_response(template)
|
rendered: str = template.render(Context({}))
|
||||||
|
|
||||||
assert rendered.count("<script") == 0 # No JS
|
assert rendered.count("<script") == 0 # No JS
|
||||||
assert rendered.count("<link") == 0 # No CSS
|
assert rendered.count("<link") == 1 # 1 placeholder link
|
||||||
assert rendered.count("<style") == 0
|
assert rendered.count("<style") == 0
|
||||||
|
|
||||||
|
assert rendered.strip() == '<link name="CSS_PLACEHOLDER">'
|
||||||
|
|
||||||
def test_single_component_dependencies(self):
|
def test_single_component_dependencies(self):
|
||||||
registry.register(name="test", component=SimpleComponent)
|
registry.register(name="test", component=SimpleComponent)
|
||||||
|
|
||||||
|
@ -178,7 +187,7 @@ class TestDependencyRendering:
|
||||||
{% component 'test' variable='foo' / %}
|
{% component 'test' variable='foo' / %}
|
||||||
"""
|
"""
|
||||||
template = Template(template_str)
|
template = Template(template_str)
|
||||||
rendered = create_and_process_template_response(template)
|
rendered = template.render(Context({}))
|
||||||
|
|
||||||
# Dependency manager script
|
# Dependency manager script
|
||||||
assertInHTML('<script src="django_components/django_components.min.js"></script>', rendered, count=1)
|
assertInHTML('<script src="django_components/django_components.min.js"></script>', rendered, count=1)
|
||||||
|
@ -213,7 +222,7 @@ class TestDependencyRendering:
|
||||||
{% component 'te-s/t' variable='foo' / %}
|
{% component 'te-s/t' variable='foo' / %}
|
||||||
"""
|
"""
|
||||||
template = Template(template_str)
|
template = Template(template_str)
|
||||||
rendered = create_and_process_template_response(template)
|
rendered = template.render(Context({}))
|
||||||
|
|
||||||
# Dependency manager script
|
# Dependency manager script
|
||||||
assertInHTML('<script src="django_components/django_components.min.js"></script>', rendered, count=1)
|
assertInHTML('<script src="django_components/django_components.min.js"></script>', rendered, count=1)
|
||||||
|
@ -248,7 +257,7 @@ class TestDependencyRendering:
|
||||||
{% component 'test' variable='foo' / %}
|
{% component 'test' variable='foo' / %}
|
||||||
"""
|
"""
|
||||||
template = Template(template_str)
|
template = Template(template_str)
|
||||||
rendered = create_and_process_template_response(template)
|
rendered = template.render(Context({}))
|
||||||
|
|
||||||
assert "_RENDERED" not in rendered
|
assert "_RENDERED" not in rendered
|
||||||
|
|
||||||
|
@ -260,7 +269,7 @@ class TestDependencyRendering:
|
||||||
{% component 'test' variable='foo' / %}
|
{% component 'test' variable='foo' / %}
|
||||||
"""
|
"""
|
||||||
template = Template(template_str)
|
template = Template(template_str)
|
||||||
rendered = create_and_process_template_response(template)
|
rendered = template.render(Context({}))
|
||||||
|
|
||||||
# Dependency manager script - NOT present
|
# Dependency manager script - NOT present
|
||||||
assertInHTML('<script src="django_components/django_components.min.js"></script>', rendered, count=0)
|
assertInHTML('<script src="django_components/django_components.min.js"></script>', rendered, count=0)
|
||||||
|
@ -279,7 +288,7 @@ class TestDependencyRendering:
|
||||||
{% component 'test' variable='foo' %}{% endcomponent %}
|
{% component 'test' variable='foo' %}{% endcomponent %}
|
||||||
"""
|
"""
|
||||||
template = Template(template_str)
|
template = Template(template_str)
|
||||||
rendered = create_and_process_template_response(template)
|
rendered = template.render(Context({}))
|
||||||
|
|
||||||
# Dependency manager script
|
# Dependency manager script
|
||||||
assertInHTML('<script src="django_components/django_components.min.js"></script>', rendered, count=1)
|
assertInHTML('<script src="django_components/django_components.min.js"></script>', rendered, count=1)
|
||||||
|
@ -315,7 +324,7 @@ class TestDependencyRendering:
|
||||||
{% component 'test' / %}
|
{% component 'test' / %}
|
||||||
"""
|
"""
|
||||||
template = Template(template_str)
|
template = Template(template_str)
|
||||||
rendered = create_and_process_template_response(template)
|
rendered = template.render(Context({}))
|
||||||
|
|
||||||
# Dependency manager script
|
# Dependency manager script
|
||||||
assertInHTML('<script src="django_components/django_components.min.js"></script>', rendered, count=1)
|
assertInHTML('<script src="django_components/django_components.min.js"></script>', rendered, count=1)
|
||||||
|
@ -372,13 +381,13 @@ class TestDependencyRendering:
|
||||||
{% component_css_dependencies %}
|
{% component_css_dependencies %}
|
||||||
"""
|
"""
|
||||||
template = Template(template_str)
|
template = Template(template_str)
|
||||||
rendered = create_and_process_template_response(template)
|
rendered: str = template.render(Context({}))
|
||||||
|
|
||||||
# Dependency manager script
|
# Dependency manager script
|
||||||
assertInHTML('<script src="django_components/django_components.min.js"></script>', rendered, count=1)
|
assertInHTML('<script src="django_components/django_components.min.js"></script>', rendered, count=0)
|
||||||
|
|
||||||
assert rendered.count("<script") == 1 # 1 boilerplate script
|
assert rendered.count("<script") == 1 # 1 placeholder script
|
||||||
assert rendered.count("<link") == 0 # No CSS
|
assert rendered.count("<link") == 1 # 1 placeholder link
|
||||||
assert rendered.count("<style") == 0
|
assert rendered.count("<style") == 0
|
||||||
|
|
||||||
assert "loadedJsUrls" not in rendered
|
assert "loadedJsUrls" not in rendered
|
||||||
|
@ -386,6 +395,11 @@ class TestDependencyRendering:
|
||||||
assert "toLoadJsTags" not in rendered
|
assert "toLoadJsTags" not in rendered
|
||||||
assert "toLoadCssTags" not in rendered
|
assert "toLoadCssTags" not in rendered
|
||||||
|
|
||||||
|
assert rendered.strip() == (
|
||||||
|
'<script name="JS_PLACEHOLDER"></script>\n'
|
||||||
|
' <link name="CSS_PLACEHOLDER">'
|
||||||
|
)
|
||||||
|
|
||||||
def test_multiple_components_dependencies(self):
|
def test_multiple_components_dependencies(self):
|
||||||
registry.register(name="inner", component=SimpleComponent)
|
registry.register(name="inner", component=SimpleComponent)
|
||||||
registry.register(name="outer", component=SimpleComponentNested)
|
registry.register(name="outer", component=SimpleComponentNested)
|
||||||
|
@ -400,7 +414,7 @@ class TestDependencyRendering:
|
||||||
{% endcomponent %}
|
{% endcomponent %}
|
||||||
"""
|
"""
|
||||||
template = Template(template_str)
|
template = Template(template_str)
|
||||||
rendered = create_and_process_template_response(template)
|
rendered: str = template.render(Context({}))
|
||||||
|
|
||||||
# Dependency manager script
|
# Dependency manager script
|
||||||
# NOTE: Should be present only ONCE!
|
# NOTE: Should be present only ONCE!
|
||||||
|
@ -499,7 +513,7 @@ class TestDependencyRendering:
|
||||||
{% component 'test' variable='variable' / %}
|
{% component 'test' variable='variable' / %}
|
||||||
"""
|
"""
|
||||||
template = Template(template_str)
|
template = Template(template_str)
|
||||||
rendered = create_and_process_template_response(template)
|
rendered = template.render(Context({}))
|
||||||
assert "_RENDERED" not in rendered
|
assert "_RENDERED" not in rendered
|
||||||
|
|
||||||
def test_adds_component_id_html_attr_single(self):
|
def test_adds_component_id_html_attr_single(self):
|
||||||
|
@ -510,7 +524,7 @@ class TestDependencyRendering:
|
||||||
{% component 'test' variable='foo' / %}
|
{% component 'test' variable='foo' / %}
|
||||||
"""
|
"""
|
||||||
template = Template(template_str)
|
template = Template(template_str)
|
||||||
rendered = create_and_process_template_response(template)
|
rendered = template.render(Context({}))
|
||||||
|
|
||||||
assertHTMLEqual(rendered, "Variable: <strong data-djc-id-ca1bc3f>foo</strong>")
|
assertHTMLEqual(rendered, "Variable: <strong data-djc-id-ca1bc3f>foo</strong>")
|
||||||
|
|
||||||
|
@ -529,7 +543,7 @@ class TestDependencyRendering:
|
||||||
{% component 'test' variable='foo' / %}
|
{% component 'test' variable='foo' / %}
|
||||||
"""
|
"""
|
||||||
template = Template(template_str)
|
template = Template(template_str)
|
||||||
rendered = create_and_process_template_response(template)
|
rendered = template.render(Context({}))
|
||||||
|
|
||||||
assertHTMLEqual(
|
assertHTMLEqual(
|
||||||
rendered,
|
rendered,
|
||||||
|
@ -565,7 +579,7 @@ class TestDependencyRendering:
|
||||||
{% component 'outer' variable='foo' / %}
|
{% component 'outer' variable='foo' / %}
|
||||||
"""
|
"""
|
||||||
template = Template(template_str)
|
template = Template(template_str)
|
||||||
rendered = create_and_process_template_response(template)
|
rendered = template.render(Context({}))
|
||||||
|
|
||||||
assertHTMLEqual(
|
assertHTMLEqual(
|
||||||
rendered,
|
rendered,
|
||||||
|
@ -605,10 +619,7 @@ class TestDependencyRendering:
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
"""
|
"""
|
||||||
template = Template(template_str)
|
template = Template(template_str)
|
||||||
rendered = create_and_process_template_response(
|
rendered = template.render(Context({"lst": range(3)}))
|
||||||
template,
|
|
||||||
context=Context({"lst": range(3)}),
|
|
||||||
)
|
|
||||||
|
|
||||||
assertHTMLEqual(
|
assertHTMLEqual(
|
||||||
rendered,
|
rendered,
|
||||||
|
|
|
@ -229,9 +229,10 @@ class TestDynamicExpr:
|
||||||
)
|
)
|
||||||
|
|
||||||
template = Template(template_str)
|
template = Template(template_str)
|
||||||
rendered = template.render(
|
rendered: str = template.render(
|
||||||
Context(
|
Context(
|
||||||
{
|
{
|
||||||
|
"DJC_DEPS_STRATEGY": "ignore",
|
||||||
"var_a": 3,
|
"var_a": 3,
|
||||||
"is_active": True,
|
"is_active": True,
|
||||||
"list": [{"a": 1}, {"a": 2}],
|
"list": [{"a": 1}, {"a": 2}],
|
||||||
|
@ -297,9 +298,10 @@ class TestDynamicExpr:
|
||||||
)
|
)
|
||||||
|
|
||||||
template = Template(template_str)
|
template = Template(template_str)
|
||||||
rendered = template.render(
|
rendered: str = template.render(
|
||||||
Context(
|
Context(
|
||||||
{
|
{
|
||||||
|
"DJC_DEPS_STRATEGY": "ignore",
|
||||||
"var_a": 3,
|
"var_a": 3,
|
||||||
"is_active": True,
|
"is_active": True,
|
||||||
"list": [{"a": 1}, {"a": 2}],
|
"list": [{"a": 1}, {"a": 2}],
|
||||||
|
|
|
@ -59,7 +59,7 @@ class TestExtendsCompat:
|
||||||
{% endcomponent %}
|
{% endcomponent %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
"""
|
"""
|
||||||
rendered = Template(template).render(Context())
|
rendered = Template(template).render(Context({"DJC_DEPS_STRATEGY": "ignore"}))
|
||||||
|
|
||||||
expected = """
|
expected = """
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
|
@ -109,7 +109,7 @@ class TestExtendsCompat:
|
||||||
{% endcomponent %}
|
{% endcomponent %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
"""
|
"""
|
||||||
rendered = Template(template).render(Context())
|
rendered = Template(template).render(Context({"DJC_DEPS_STRATEGY": "ignore"}))
|
||||||
|
|
||||||
expected = """
|
expected = """
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
|
@ -176,7 +176,7 @@ class TestExtendsCompat:
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
"""
|
"""
|
||||||
template = Template(template_str)
|
template = Template(template_str)
|
||||||
rendered = template.render(Context())
|
rendered = template.render(Context({"DJC_DEPS_STRATEGY": "ignore"}))
|
||||||
|
|
||||||
expected = """
|
expected = """
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
|
@ -242,7 +242,7 @@ class TestExtendsCompat:
|
||||||
{% endcomponent %}
|
{% endcomponent %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
"""
|
"""
|
||||||
rendered = Template(template).render(Context())
|
rendered = Template(template).render(Context({"DJC_DEPS_STRATEGY": "ignore"}))
|
||||||
|
|
||||||
expected = """
|
expected = """
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
|
@ -295,7 +295,7 @@ class TestExtendsCompat:
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
"""
|
"""
|
||||||
rendered = Template(template).render(Context())
|
rendered = Template(template).render(Context({"DJC_DEPS_STRATEGY": "ignore"}))
|
||||||
|
|
||||||
expected = """
|
expected = """
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
|
@ -343,7 +343,7 @@ class TestExtendsCompat:
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
"""
|
"""
|
||||||
rendered = Template(template).render(Context())
|
rendered = Template(template).render(Context({"DJC_DEPS_STRATEGY": "ignore"}))
|
||||||
|
|
||||||
expected = """
|
expected = """
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
|
@ -395,7 +395,7 @@ class TestExtendsCompat:
|
||||||
{% endcomponent %}
|
{% endcomponent %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
"""
|
"""
|
||||||
rendered = Template(template).render(Context())
|
rendered = Template(template).render(Context({"DJC_DEPS_STRATEGY": "ignore"}))
|
||||||
|
|
||||||
expected = """
|
expected = """
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
|
@ -440,7 +440,7 @@ class TestExtendsCompat:
|
||||||
{% component "extended_component" / %}
|
{% component "extended_component" / %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
"""
|
"""
|
||||||
rendered = Template(template).render(Context())
|
rendered = Template(template).render(Context({"DJC_DEPS_STRATEGY": "ignore"}))
|
||||||
|
|
||||||
expected = """
|
expected = """
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
|
@ -458,7 +458,7 @@ class TestExtendsCompat:
|
||||||
assertHTMLEqual(rendered, expected)
|
assertHTMLEqual(rendered, expected)
|
||||||
|
|
||||||
# second rendering after cache built
|
# second rendering after cache built
|
||||||
rendered_2 = Template(template).render(Context())
|
rendered_2 = Template(template).render(Context({"DJC_DEPS_STRATEGY": "ignore"}))
|
||||||
expected_2 = expected.replace("data-djc-id-ca1bc3f", "data-djc-id-ca1bc41")
|
expected_2 = expected.replace("data-djc-id-ca1bc3f", "data-djc-id-ca1bc41")
|
||||||
assertHTMLEqual(rendered_2, expected_2)
|
assertHTMLEqual(rendered_2, expected_2)
|
||||||
|
|
||||||
|
@ -480,7 +480,7 @@ class TestExtendsCompat:
|
||||||
{% endfill %}
|
{% endfill %}
|
||||||
{% endcomponent %}
|
{% endcomponent %}
|
||||||
"""
|
"""
|
||||||
rendered = Template(template).render(Context())
|
rendered = Template(template).render(Context({"DJC_DEPS_STRATEGY": "ignore"}))
|
||||||
expected = """
|
expected = """
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html data-djc-id-ca1bc40 lang="en">
|
<html data-djc-id-ca1bc40 lang="en">
|
||||||
|
@ -513,7 +513,7 @@ class TestExtendsCompat:
|
||||||
{% endfill %}
|
{% endfill %}
|
||||||
{% endcomponent %}
|
{% endcomponent %}
|
||||||
"""
|
"""
|
||||||
rendered = Template(template).render(Context())
|
rendered = Template(template).render(Context({"DJC_DEPS_STRATEGY": "ignore"}))
|
||||||
expected = """
|
expected = """
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html data-djc-id-ca1bc40 lang="en">
|
<html data-djc-id-ca1bc40 lang="en">
|
||||||
|
@ -544,7 +544,7 @@ class TestExtendsCompat:
|
||||||
{% endcomponent %}
|
{% endcomponent %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
"""
|
"""
|
||||||
rendered = Template(template).render(Context())
|
rendered = Template(template).render(Context({"DJC_DEPS_STRATEGY": "ignore"}))
|
||||||
expected = """
|
expected = """
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
|
@ -575,7 +575,7 @@ class TestExtendsCompat:
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
"""
|
"""
|
||||||
rendered = Template(template).render(Context())
|
rendered = Template(template).render(Context({"DJC_DEPS_STRATEGY": "ignore"}))
|
||||||
expected = """
|
expected = """
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
|
@ -604,7 +604,7 @@ class TestExtendsCompat:
|
||||||
{% load component_tags %}
|
{% load component_tags %}
|
||||||
{% component "block_in_component_parent" %}{% endcomponent %}
|
{% component "block_in_component_parent" %}{% endcomponent %}
|
||||||
"""
|
"""
|
||||||
rendered = Template(template).render(Context())
|
rendered = Template(template).render(Context({"DJC_DEPS_STRATEGY": "ignore"}))
|
||||||
expected = """
|
expected = """
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html data-djc-id-ca1bc3f lang="en">
|
<html data-djc-id-ca1bc3f lang="en">
|
||||||
|
@ -644,7 +644,7 @@ class TestExtendsCompat:
|
||||||
wow
|
wow
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
"""
|
"""
|
||||||
rendered = Template(template).render(Context())
|
rendered = Template(template).render(Context({"DJC_DEPS_STRATEGY": "ignore"}))
|
||||||
expected = """
|
expected = """
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html data-djc-id-ca1bc40 lang="en">
|
<html data-djc-id-ca1bc40 lang="en">
|
||||||
|
@ -674,7 +674,7 @@ class TestExtendsCompat:
|
||||||
{% load component_tags %}
|
{% load component_tags %}
|
||||||
{% component "slot_inside_block" %}{% endcomponent %}
|
{% component "slot_inside_block" %}{% endcomponent %}
|
||||||
"""
|
"""
|
||||||
rendered = Template(template).render(Context())
|
rendered = Template(template).render(Context({"DJC_DEPS_STRATEGY": "ignore"}))
|
||||||
expected = """
|
expected = """
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html data-djc-id-ca1bc3f lang="en">
|
<html data-djc-id-ca1bc3f lang="en">
|
||||||
|
@ -710,7 +710,7 @@ class TestExtendsCompat:
|
||||||
{% load component_tags %}
|
{% load component_tags %}
|
||||||
{% component "slot_inside_block" %}{% endcomponent %}
|
{% component "slot_inside_block" %}{% endcomponent %}
|
||||||
"""
|
"""
|
||||||
rendered = Template(template).render(Context())
|
rendered = Template(template).render(Context({"DJC_DEPS_STRATEGY": "ignore"}))
|
||||||
expected = """
|
expected = """
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html data-djc-id-ca1bc3f lang="en">
|
<html data-djc-id-ca1bc3f lang="en">
|
||||||
|
@ -746,7 +746,7 @@ class TestExtendsCompat:
|
||||||
{% endfill %}
|
{% endfill %}
|
||||||
{% endcomponent %}
|
{% endcomponent %}
|
||||||
"""
|
"""
|
||||||
rendered = Template(template).render(Context())
|
rendered = Template(template).render(Context({"DJC_DEPS_STRATEGY": "ignore"}))
|
||||||
expected = """
|
expected = """
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html data-djc-id-ca1bc40 lang="en">
|
<html data-djc-id-ca1bc40 lang="en">
|
||||||
|
@ -792,7 +792,7 @@ class TestExtendsCompat:
|
||||||
{% endfill %}
|
{% endfill %}
|
||||||
{% endcomponent %}
|
{% endcomponent %}
|
||||||
"""
|
"""
|
||||||
rendered = Template(template).render(Context())
|
rendered = Template(template).render(Context({"DJC_DEPS_STRATEGY": "ignore"}))
|
||||||
expected = """
|
expected = """
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html data-djc-id-ca1bc41 lang="en">
|
<html data-djc-id-ca1bc41 lang="en">
|
||||||
|
@ -832,7 +832,7 @@ class TestExtendsCompat:
|
||||||
{% endcomponent %}
|
{% endcomponent %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
"""
|
"""
|
||||||
rendered = Template(template).render(Context())
|
rendered = Template(template).render(Context({"DJC_DEPS_STRATEGY": "ignore"}))
|
||||||
expected = """
|
expected = """
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
|
@ -857,7 +857,7 @@ class TestExtendsCompat:
|
||||||
{% load component_tags %}
|
{% load component_tags %}
|
||||||
{% component "relative_file_component_using_template_file" %}{% endcomponent %}
|
{% component "relative_file_component_using_template_file" %}{% endcomponent %}
|
||||||
"""
|
"""
|
||||||
rendered = Template(template).render(Context())
|
rendered = Template(template).render(Context({"DJC_DEPS_STRATEGY": "ignore"}))
|
||||||
expected = """
|
expected = """
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html data-djc-id-ca1bc3f="" lang="en">
|
<html data-djc-id-ca1bc3f="" lang="en">
|
||||||
|
@ -880,7 +880,7 @@ class TestExtendsCompat:
|
||||||
{% load component_tags %}
|
{% load component_tags %}
|
||||||
{% component "relative_file_component_using_get_template_name" %}{% endcomponent %}
|
{% component "relative_file_component_using_get_template_name" %}{% endcomponent %}
|
||||||
"""
|
"""
|
||||||
rendered = Template(template).render(Context())
|
rendered = Template(template).render(Context({"DJC_DEPS_STRATEGY": "ignore"}))
|
||||||
expected = """
|
expected = """
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html data-djc-id-ca1bc3f="" lang="en">
|
<html data-djc-id-ca1bc3f="" lang="en">
|
||||||
|
|
|
@ -311,7 +311,7 @@ class TestComponentSlot:
|
||||||
"""
|
"""
|
||||||
self.template = Template(template_str)
|
self.template = Template(template_str)
|
||||||
|
|
||||||
nested_ctx = Context()
|
nested_ctx = Context({"DJC_DEPS_STRATEGY": "ignore"})
|
||||||
# Check that the component can access vars across different context layers
|
# Check that the component can access vars across different context layers
|
||||||
nested_ctx.push({"some": "var"})
|
nested_ctx.push({"some": "var"})
|
||||||
nested_ctx.push({"name": "carl"})
|
nested_ctx.push({"name": "carl"})
|
||||||
|
@ -470,7 +470,7 @@ class TestComponentSlot:
|
||||||
slots={
|
slots={
|
||||||
"main": "MAIN",
|
"main": "MAIN",
|
||||||
},
|
},
|
||||||
render_dependencies=False,
|
deps_strategy="ignore",
|
||||||
)
|
)
|
||||||
|
|
||||||
# 2. Specify the non-required slot by the "default" name
|
# 2. Specify the non-required slot by the "default" name
|
||||||
|
@ -479,7 +479,7 @@ class TestComponentSlot:
|
||||||
slots={
|
slots={
|
||||||
"default": "MAIN",
|
"default": "MAIN",
|
||||||
},
|
},
|
||||||
render_dependencies=False,
|
deps_strategy="ignore",
|
||||||
)
|
)
|
||||||
|
|
||||||
assertHTMLEqual(rendered1, "<div data-djc-id-ca1bc3e><div>MAIN</div></div>")
|
assertHTMLEqual(rendered1, "<div data-djc-id-ca1bc3e><div>MAIN</div></div>")
|
||||||
|
@ -491,7 +491,7 @@ class TestComponentSlot:
|
||||||
slots={
|
slots={
|
||||||
"main": "MAIN",
|
"main": "MAIN",
|
||||||
},
|
},
|
||||||
render_dependencies=False,
|
deps_strategy="ignore",
|
||||||
)
|
)
|
||||||
assertHTMLEqual(rendered3, "<div data-djc-id-ca1bc42><main>MAIN</main><div>MAIN</div></div>")
|
assertHTMLEqual(rendered3, "<div data-djc-id-ca1bc42><main>MAIN</main><div>MAIN</div></div>")
|
||||||
|
|
||||||
|
@ -506,7 +506,7 @@ class TestComponentSlot:
|
||||||
slots={
|
slots={
|
||||||
"default": "MAIN",
|
"default": "MAIN",
|
||||||
},
|
},
|
||||||
render_dependencies=False,
|
deps_strategy="ignore",
|
||||||
)
|
)
|
||||||
|
|
||||||
@djc_test(parametrize=PARAMETRIZE_CONTEXT_BEHAVIOR)
|
@djc_test(parametrize=PARAMETRIZE_CONTEXT_BEHAVIOR)
|
||||||
|
|
|
@ -1,13 +1,8 @@
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Dict, Optional
|
from typing import Dict, Optional
|
||||||
from unittest.mock import Mock
|
|
||||||
|
|
||||||
import django
|
import django
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.template import Context
|
|
||||||
from django.template.response import TemplateResponse
|
|
||||||
|
|
||||||
from django_components.middleware import ComponentDependencyMiddleware
|
|
||||||
|
|
||||||
# Common use case in our tests is to check that the component works in both
|
# Common use case in our tests is to check that the component works in both
|
||||||
# "django" and "isolated" context behaviors. If you need only that, pass this
|
# "django" and "isolated" context behaviors. If you need only that, pass this
|
||||||
|
@ -22,28 +17,6 @@ PARAMETRIZE_CONTEXT_BEHAVIOR = (
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
# Create middleware instance
|
|
||||||
response_stash = None
|
|
||||||
middleware = ComponentDependencyMiddleware(get_response=lambda _: response_stash)
|
|
||||||
|
|
||||||
request = Mock()
|
|
||||||
mock_template = Mock()
|
|
||||||
|
|
||||||
|
|
||||||
def create_and_process_template_response(template, context=None, use_middleware=True):
|
|
||||||
context = context if context is not None else Context({})
|
|
||||||
mock_template.render = lambda context, _: template.render(context)
|
|
||||||
response = TemplateResponse(request, mock_template, context)
|
|
||||||
if use_middleware:
|
|
||||||
response.render()
|
|
||||||
global response_stash
|
|
||||||
response_stash = response
|
|
||||||
response = middleware(request)
|
|
||||||
else:
|
|
||||||
response.render()
|
|
||||||
return response.content.decode("utf-8")
|
|
||||||
|
|
||||||
|
|
||||||
def setup_test_config(
|
def setup_test_config(
|
||||||
components: Optional[Dict] = None,
|
components: Optional[Dict] = None,
|
||||||
extra_settings: Optional[Dict] = None,
|
extra_settings: Optional[Dict] = None,
|
||||||
|
@ -72,7 +45,7 @@ def setup_test_config(
|
||||||
"template_cache_size": 128,
|
"template_cache_size": 128,
|
||||||
**(components or {}),
|
**(components or {}),
|
||||||
},
|
},
|
||||||
"MIDDLEWARE": ["django_components.middleware.ComponentDependencyMiddleware"],
|
"MIDDLEWARE": [],
|
||||||
"DATABASES": {
|
"DATABASES": {
|
||||||
"default": {
|
"default": {
|
||||||
"ENGINE": "django.db.backends.sqlite3",
|
"ENGINE": "django.db.backends.sqlite3",
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue