Compare commits

..

No commits in common. "master" and "0.135" have entirely different histories.

258 changed files with 7953 additions and 24325 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

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

View file

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

View file

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

View file

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

8
.gitignore vendored
View file

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

File diff suppressed because it is too large Load diff

304
README.md
View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

File diff suppressed because one or more lines are too long

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

File diff suppressed because it is too large Load diff

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

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