mirror of
https://github.com/django-components/django-components.git
synced 2025-08-18 13:10:13 +00:00
refactor: rename template_name to template_file (#878)
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
This commit is contained in:
parent
b99e32e9d5
commit
d94a459c8d
29 changed files with 251 additions and 138 deletions
|
@ -20,6 +20,12 @@
|
||||||
|
|
||||||
#### Refactor
|
#### Refactor
|
||||||
|
|
||||||
|
- The canonical way to define a template file was changed from `template_name` to `template_file`,
|
||||||
|
to align with the rest of the API.
|
||||||
|
|
||||||
|
`template_name` remains for backwards compatibility. When you get / set `template_name`,
|
||||||
|
internally this is proxied to `template_file`.
|
||||||
|
|
||||||
- The undocumented `Component.component_id` was removed. Instead, use `Component.id`. Changes:
|
- The undocumented `Component.component_id` was removed. Instead, use `Component.id`. Changes:
|
||||||
|
|
||||||
- While `component_id` was unique every time you instantiated `Component`, the new `id` is unique
|
- While `component_id` was unique every time you instantiated `Component`, the new `id` is unique
|
||||||
|
|
10
README.md
10
README.md
|
@ -18,7 +18,7 @@ A component in django-components can be as simple as a Django template and Pytho
|
||||||
from django_components import Component
|
from django_components import Component
|
||||||
|
|
||||||
class Calendar(Component):
|
class Calendar(Component):
|
||||||
template_name = "calendar.html"
|
template_file = "calendar.html"
|
||||||
```
|
```
|
||||||
|
|
||||||
Or a combination of Django template, Python, CSS, and Javascript:
|
Or a combination of Django template, Python, CSS, and Javascript:
|
||||||
|
@ -46,7 +46,7 @@ document.querySelector(".calendar").onclick = function () {
|
||||||
from django_components import Component
|
from django_components import Component
|
||||||
|
|
||||||
class Calendar(Component):
|
class Calendar(Component):
|
||||||
template_name = "calendar.html"
|
template_file = "calendar.html"
|
||||||
js_file = "calendar.js"
|
js_file = "calendar.js"
|
||||||
css_file = "calendar.css"
|
css_file = "calendar.css"
|
||||||
```
|
```
|
||||||
|
@ -77,7 +77,6 @@ class Calendar(Component):
|
||||||
"""
|
"""
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
1. 🧩 **Reusability:** Allows creation of self-contained, reusable UI elements.
|
1. 🧩 **Reusability:** Allows creation of self-contained, reusable UI elements.
|
||||||
|
@ -97,13 +96,14 @@ Django-components can be particularly useful for larger Django projects that req
|
||||||
|
|
||||||
## Quickstart
|
## Quickstart
|
||||||
|
|
||||||
django-components lets you create reusable blocks of code needed to generate the front end code you need for a modern app.
|
django-components lets you create reusable blocks of code needed to generate the front end code you need for a modern app.
|
||||||
|
|
||||||
Define a component in `components/calendar/calendar.py` like this:
|
Define a component in `components/calendar/calendar.py` like this:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
@register("calendar")
|
@register("calendar")
|
||||||
class Calendar(Component):
|
class Calendar(Component):
|
||||||
template_name = "template.html"
|
template_file = "template.html"
|
||||||
|
|
||||||
def get_context_data(self, date):
|
def get_context_data(self, date):
|
||||||
return {"date": date}
|
return {"date": date}
|
||||||
|
|
|
@ -14,7 +14,7 @@ from django_components import Component, register
|
||||||
|
|
||||||
@register("calendar")
|
@register("calendar")
|
||||||
class Calendar(Component):
|
class Calendar(Component):
|
||||||
template_name = "template.html"
|
template_file = "template.html"
|
||||||
|
|
||||||
# This component takes one parameter, a date string to show in the template
|
# This component takes one parameter, a date string to show in the template
|
||||||
def get_context_data(self, date):
|
def get_context_data(self, date):
|
||||||
|
@ -127,6 +127,7 @@ NOTE: The Library instance can be accessed under `library` attribute of `Compone
|
||||||
When you are creating an instance of `ComponentRegistry`, you can define the components' behavior within the template.
|
When you are creating an instance of `ComponentRegistry`, you can define the components' behavior within the template.
|
||||||
|
|
||||||
The registry accepts these settings:
|
The registry accepts these settings:
|
||||||
|
|
||||||
- `context_behavior`
|
- `context_behavior`
|
||||||
- `tag_formatter`
|
- `tag_formatter`
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,7 @@ HTML / JS / CSS with a component:
|
||||||
[`Component.css`](../../reference/api.md#django_components.Component.css) and
|
[`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
|
[`Component.js`](../../reference/api.md#django_components.Component.js) to define the main HTML / CSS / JS for a component
|
||||||
as inlined code.
|
as inlined code.
|
||||||
- You can set [`Component.template_name`](../../reference/api.md#django_components.Component.template_name),
|
- 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.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
|
[`Component.js_file`](../../reference/api.md#django_components.Component.js_file) to define the main HTML / CSS / JS
|
||||||
for a component in separate files.
|
for a component in separate files.
|
||||||
|
@ -22,7 +22,7 @@ HTML / JS / CSS with a component:
|
||||||
|
|
||||||
You **cannot** use both inlined code **and** separate file for a single language type:
|
You **cannot** use both inlined code **and** separate file for a single language type:
|
||||||
|
|
||||||
- You can only either set `Component.template` or `Component.template_name`
|
- 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.css` or `Component.css_file`
|
||||||
- You can only either set `Component.js` or `Component.js_file`
|
- You can only either set `Component.js` or `Component.js_file`
|
||||||
|
|
||||||
|
@ -51,7 +51,7 @@ HTML / JS / CSS with a component:
|
||||||
|
|
||||||
As seen in the [getting started example](../getting_started/your_first_component.md), to associate HTML / JS / CSS
|
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
|
files with a component, you can set them as
|
||||||
[`Component.template_name`](../../reference/api.md#django_components.Component.template_name),
|
[`Component.template_file`](../../reference/api.md#django_components.Component.template_file),
|
||||||
[`Component.js_file`](../../reference/api.md#django_components.Component.js_file)
|
[`Component.js_file`](../../reference/api.md#django_components.Component.js_file)
|
||||||
and
|
and
|
||||||
[`Component.css_file`](../../reference/api.md#django_components.Component.css_file) respectively:
|
[`Component.css_file`](../../reference/api.md#django_components.Component.css_file) respectively:
|
||||||
|
@ -61,7 +61,7 @@ from django_components import Component, register
|
||||||
|
|
||||||
@register("calendar")
|
@register("calendar")
|
||||||
class Calendar(Component):
|
class Calendar(Component):
|
||||||
template_name = "template.html"
|
template_file = "template.html"
|
||||||
css_file = "style.css"
|
css_file = "style.css"
|
||||||
js_file = "script.js"
|
js_file = "script.js"
|
||||||
```
|
```
|
||||||
|
@ -70,7 +70,7 @@ In the example above, we defined the files relative to the directory where the c
|
||||||
|
|
||||||
Alternatively, you can specify the file paths relative to the directories set in
|
Alternatively, you can specify the file paths relative to the directories set in
|
||||||
[`COMPONENTS.dirs`](../../reference/settings.md#django_components.app_settings.ComponentsSettings.dirs)
|
[`COMPONENTS.dirs`](../../reference/settings.md#django_components.app_settings.ComponentsSettings.dirs)
|
||||||
or
|
or
|
||||||
[`COMPONENTS.app_dirs`](../../reference/settings.md#django_components.app_settings.ComponentsSettings.app_dirs).
|
[`COMPONENTS.app_dirs`](../../reference/settings.md#django_components.app_settings.ComponentsSettings.app_dirs).
|
||||||
|
|
||||||
If you specify the paths relative to component's directory, django-componenents does the conversion automatically
|
If you specify the paths relative to component's directory, django-componenents does the conversion automatically
|
||||||
|
@ -85,7 +85,7 @@ from django_components import Component, register
|
||||||
|
|
||||||
@register("calendar")
|
@register("calendar")
|
||||||
class Calendar(Component):
|
class Calendar(Component):
|
||||||
template_name = "calendar/template.html"
|
template_file = "calendar/template.html"
|
||||||
css_file = "calendar/style.css"
|
css_file = "calendar/style.css"
|
||||||
js_file = "calendar/script.js"
|
js_file = "calendar/script.js"
|
||||||
```
|
```
|
||||||
|
@ -94,59 +94,59 @@ class Calendar(Component):
|
||||||
|
|
||||||
**File path resolution in-depth**
|
**File path resolution in-depth**
|
||||||
|
|
||||||
At component class creation, django-components checks all file paths defined on the component (e.g. `Component.template_name`).
|
At component class creation, django-components checks all file paths defined on the component (e.g. `Component.template_file`).
|
||||||
|
|
||||||
For each file path, it checks if the file path is relative to the component's directory.
|
For each file path, it checks if the file path is relative to the component's directory.
|
||||||
And such file exists, the component's file path is re-written to be defined relative to a first matching directory
|
And such file exists, the component's file path is re-written to be defined relative to a first matching directory
|
||||||
in [`COMPONENTS.dirs`](../../reference/settings.md#django_components.app_settings.ComponentsSettings.dirs)
|
in [`COMPONENTS.dirs`](../../reference/settings.md#django_components.app_settings.ComponentsSettings.dirs)
|
||||||
or
|
or
|
||||||
[`COMPONENTS.app_dirs`](../../reference/settings.md#django_components.app_settings.ComponentsSettings.app_dirs).
|
[`COMPONENTS.app_dirs`](../../reference/settings.md#django_components.app_settings.ComponentsSettings.app_dirs).
|
||||||
|
|
||||||
**Example:**
|
**Example:**
|
||||||
|
|
||||||
```py title="[root]/components/mytable/mytable.py"
|
```py title="[root]/components/mytable/mytable.py"
|
||||||
class MyTable(Component):
|
class MyTable(Component):
|
||||||
template_name = "mytable.html"
|
template_file = "mytable.html"
|
||||||
```
|
```
|
||||||
|
|
||||||
1. Component `MyTable` is defined in file `[root]/components/mytable/mytable.py`.
|
1. Component `MyTable` is defined in file `[root]/components/mytable/mytable.py`.
|
||||||
2. The component's directory is thus `[root]/components/mytable/`.
|
2. The component's directory is thus `[root]/components/mytable/`.
|
||||||
3. Because `MyTable.template_name` is `mytable.html`, django-components tries to
|
3. Because `MyTable.template_file` is `mytable.html`, django-components tries to
|
||||||
resolve it as `[root]/components/mytable/mytable.html`.
|
resolve it as `[root]/components/mytable/mytable.html`.
|
||||||
4. django-components checks the filesystem. If there's no such file, nothing happens.
|
4. django-components checks the filesystem. If there's no such file, nothing happens.
|
||||||
5. If there IS such file, django-components tries to rewrite the path.
|
5. If there IS such file, django-components tries to rewrite the path.
|
||||||
6. django-components searches `COMPONENTS.dirs` and `COMPONENTS.app_dirs` for a first
|
6. django-components searches `COMPONENTS.dirs` and `COMPONENTS.app_dirs` for a first
|
||||||
directory that contains `[root]/components/mytable/mytable.html`.
|
directory that contains `[root]/components/mytable/mytable.html`.
|
||||||
7. It comes across `[root]/components/`, which DOES contain the path to `mytable.html`.
|
7. It comes across `[root]/components/`, which DOES contain the path to `mytable.html`.
|
||||||
8. Thus, it rewrites `template_name` from `mytable.html` to `mytable/mytable.html`.
|
8. Thus, it rewrites `template_file` from `mytable.html` to `mytable/mytable.html`.
|
||||||
|
|
||||||
NOTE: In case of ambiguity, the preference goes to resolving the files relative to the component's directory.
|
NOTE: In case of ambiguity, the preference goes to resolving the files relative to the component's directory.
|
||||||
|
|
||||||
## Defining additional JS and CSS files
|
## Defining additional JS and CSS files
|
||||||
|
|
||||||
Each component can have only a single template, and single main JS and CSS. However, you can define additional JS or CSS
|
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).
|
using the nested [`Component.Media` class](../../../reference/api#django_components.Component.Media).
|
||||||
|
|
||||||
This `Media` class behaves similarly to
|
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):
|
[Django's Media class](https://docs.djangoproject.com/en/5.1/topics/forms/media/#assets-as-a-static-definition):
|
||||||
|
|
||||||
- Paths are generally handled as static file paths, and resolved URLs are rendered to HTML with
|
- 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()`.
|
`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.
|
- 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()`.
|
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),
|
- 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
|
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()`.
|
resolution and rendering with `media_class.render_js()` or `media_class.render_css()`.
|
||||||
|
|
||||||
However, there's a few differences from Django's Media class:
|
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,
|
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)).
|
or (CSS-only) a dictonary (See [`ComponentMediaInput`](../../../reference/api#django_components.ComponentMediaInput)).
|
||||||
2. Individual JS / CSS files can be any of `str`, `bytes`, `Path`,
|
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
|
[`SafeString`](https://docs.djangoproject.com/en/5.1/ref/utils/#django.utils.safestring.SafeString), or a function
|
||||||
(See [`ComponentMediaInputPath`](../../../reference/api#django_components.ComponentMediaInputPath)).
|
(See [`ComponentMediaInputPath`](../../../reference/api#django_components.ComponentMediaInputPath)).
|
||||||
3. Our Media class does NOT support
|
3. Our Media class does NOT support
|
||||||
[Django's `extend` keyword](https://docs.djangoproject.com/en/5.1/topics/forms/media/#extend)
|
[Django's `extend` keyword](https://docs.djangoproject.com/en/5.1/topics/forms/media/#extend)
|
||||||
|
|
||||||
```py
|
```py
|
||||||
class MyTable(Component):
|
class MyTable(Component):
|
||||||
|
@ -268,7 +268,7 @@ class ModuleJsPath:
|
||||||
|
|
||||||
@register("calendar")
|
@register("calendar")
|
||||||
class Calendar(Component):
|
class Calendar(Component):
|
||||||
template_name = "calendar/template.html"
|
template_file = "calendar/template.html"
|
||||||
|
|
||||||
def get_context_data(self, date):
|
def get_context_data(self, date):
|
||||||
return {
|
return {
|
||||||
|
@ -314,7 +314,7 @@ class MyMedia(Media):
|
||||||
|
|
||||||
@register("calendar")
|
@register("calendar")
|
||||||
class Calendar(Component):
|
class Calendar(Component):
|
||||||
template_name = "calendar/template.html"
|
template_file = "calendar/template.html"
|
||||||
css_file = "calendar/style.css"
|
css_file = "calendar/style.css"
|
||||||
js_file = "calendar/script.js"
|
js_file = "calendar/script.js"
|
||||||
|
|
||||||
|
@ -331,27 +331,28 @@ class Calendar(Component):
|
||||||
Component's HTML / CSS / JS is resolved and loaded lazily.
|
Component's HTML / CSS / JS is resolved and loaded lazily.
|
||||||
|
|
||||||
This means that, when you specify any of
|
This means that, when you specify any of
|
||||||
[`template_name`](../../reference/api.md#django_components.Component.template_name),
|
[`template_file`](../../reference/api.md#django_components.Component.template_file),
|
||||||
[`js_file`](../../reference/api.md#django_components.Component.js_file),
|
[`js_file`](../../reference/api.md#django_components.Component.js_file),
|
||||||
[`css_file`](../../reference/api.md#django_components.Component.css_file),
|
[`css_file`](../../reference/api.md#django_components.Component.css_file),
|
||||||
or [`Media.js/css`](../../reference/api.md#django_components.Component.Media),
|
or [`Media.js/css`](../../reference/api.md#django_components.Component.Media),
|
||||||
these file paths will be resolved only once you either:
|
these file paths will be resolved only once you either:
|
||||||
|
|
||||||
1. Access any of the following attributes on the component:
|
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),
|
- [`media`](../../reference/api.md#django_components.Component.media),
|
||||||
[`template_name`](../../reference/api.md#django_components.Component.template_name),
|
[`template`](../../reference/api.md#django_components.Component.template),
|
||||||
[`js`](../../reference/api.md#django_components.Component.js),
|
[`template_file`](../../reference/api.md#django_components.Component.template_file),
|
||||||
[`js_file`](../../reference/api.md#django_components.Component.js_file),
|
[`js`](../../reference/api.md#django_components.Component.js),
|
||||||
[`css`](../../reference/api.md#django_components.Component.css),
|
[`js_file`](../../reference/api.md#django_components.Component.js_file),
|
||||||
[`css_file`](../../reference/api.md#django_components.Component.css_file)
|
[`css`](../../reference/api.md#django_components.Component.css),
|
||||||
|
[`css_file`](../../reference/api.md#django_components.Component.css_file)
|
||||||
|
|
||||||
2. Render the component.
|
2. Render the component.
|
||||||
|
|
||||||
Once the component's media files have been loaded once, they will remain in-memory
|
Once the component's media files have been loaded once, they will remain in-memory
|
||||||
on the Component class:
|
on the Component class:
|
||||||
|
|
||||||
- HTML from [`Component.template_name`](../../reference/api.md#django_components.Component.template_name)
|
- 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)
|
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)
|
- 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)
|
will be available under [`Component.css`](../../reference/api.md#django_components.Component.css)
|
||||||
|
@ -359,7 +360,7 @@ on the Component class:
|
||||||
will be available under [`Component.js`](../../reference/api.md#django_components.Component.js)
|
will be available under [`Component.js`](../../reference/api.md#django_components.Component.js)
|
||||||
|
|
||||||
Thus, whether you define HTML via
|
Thus, whether you define HTML via
|
||||||
[`Component.template_name`](../../reference/api.md#django_components.Component.template_name)
|
[`Component.template_file`](../../reference/api.md#django_components.Component.template_file)
|
||||||
or [`Component.template`](../../reference/api.md#django_components.Component.template),
|
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).
|
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.
|
And the same applies for JS and CSS.
|
||||||
|
@ -371,7 +372,7 @@ And the same applies for JS and CSS.
|
||||||
# are not yet loaded!
|
# are not yet loaded!
|
||||||
@register("calendar")
|
@register("calendar")
|
||||||
class Calendar(Component):
|
class Calendar(Component):
|
||||||
template_name = "calendar/template.html"
|
template_file = "calendar/template.html"
|
||||||
css_file = "calendar/style.css"
|
css_file = "calendar/style.css"
|
||||||
js_file = "calendar/script.js"
|
js_file = "calendar/script.js"
|
||||||
|
|
||||||
|
@ -395,7 +396,7 @@ print(Calendar.css)
|
||||||
django-components assumes that the component's media files like `js_file` or `Media.js/css` are static.
|
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.
|
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
|
Modifying these files AFTER the component has been loaded at best does nothing. However, this is
|
||||||
an untested behavior.
|
an untested behavior.
|
||||||
|
|
||||||
|
|
|
@ -3,10 +3,13 @@ title: Single-file components
|
||||||
weight: 1
|
weight: 1
|
||||||
---
|
---
|
||||||
|
|
||||||
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_name` and `Media`. For example, here's the calendar component from above, defined in a single 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,
|
||||||
|
defined in a single file:
|
||||||
|
|
||||||
```python title="[project root]/components/calendar.py"
|
```python title="[project root]/components/calendar.py"
|
||||||
# In a file called [project root]/components/calendar.py
|
|
||||||
from django_components import Component, register, types
|
from django_components import Component, register, types
|
||||||
|
|
||||||
@register("calendar")
|
@register("calendar")
|
||||||
|
@ -17,18 +20,27 @@ class Calendar(Component):
|
||||||
}
|
}
|
||||||
|
|
||||||
template: types.django_html = """
|
template: types.django_html = """
|
||||||
<div class="calendar-component">Today's date is <span>{{ date }}</span></div>
|
<div class="calendar">
|
||||||
|
Today's date is <span>{{ date }}</span>
|
||||||
|
</div>
|
||||||
"""
|
"""
|
||||||
|
|
||||||
css: types.css = """
|
css: types.css = """
|
||||||
.calendar-component { width: 200px; background: pink; }
|
.calendar {
|
||||||
.calendar-component span { font-weight: bold; }
|
width: 200px;
|
||||||
|
background: pink;
|
||||||
|
}
|
||||||
|
.calendar span {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
"""
|
"""
|
||||||
|
|
||||||
js: types.js = """
|
js: types.js = """
|
||||||
(function(){
|
(function(){
|
||||||
if (document.querySelector(".calendar-component")) {
|
if (document.querySelector(".calendar")) {
|
||||||
document.querySelector(".calendar-component").onclick = function(){ alert("Clicked calendar!"); };
|
document.querySelector(".calendar").onclick = () => {
|
||||||
|
alert("Clicked calendar!");
|
||||||
|
};
|
||||||
}
|
}
|
||||||
})()
|
})()
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -49,6 +49,7 @@ Inside `calendar.css`, write:
|
||||||
Be sure to prefix your rules with unique CSS class like `calendar`, so the CSS doesn't clash with other rules.
|
Be sure to prefix your rules with unique CSS class like `calendar`, so the CSS doesn't clash with other rules.
|
||||||
|
|
||||||
<!-- TODO: UPDATE AFTER SCOPED CSS ADDED -->
|
<!-- TODO: UPDATE AFTER SCOPED CSS ADDED -->
|
||||||
|
|
||||||
!!! note
|
!!! note
|
||||||
|
|
||||||
Soon, django-components will automatically scope your CSS by default, so you won't have to worry
|
Soon, django-components will automatically scope your CSS by default, so you won't have to worry
|
||||||
|
@ -99,6 +100,7 @@ an [anonymous self-invoking function](https://developer.mozilla.org/en-US/docs/G
|
||||||
This makes all variables defined only be defined inside this component and not affect other components.
|
This makes all variables defined only be defined inside this component and not affect other components.
|
||||||
|
|
||||||
<!-- TODO: UPDATE AFTER FUNCTIONS WRAPPED -->
|
<!-- TODO: UPDATE AFTER FUNCTIONS WRAPPED -->
|
||||||
|
|
||||||
!!! note
|
!!! note
|
||||||
|
|
||||||
Soon, django-components will automatically wrap your JS in a self-invoking function by default
|
Soon, django-components will automatically wrap your JS in a self-invoking function by default
|
||||||
|
@ -172,7 +174,6 @@ So in your HTML, you may see something like this:
|
||||||
</html>
|
</html>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
### 4. Link JS and CSS to a component
|
### 4. Link JS and CSS to a component
|
||||||
|
|
||||||
Finally, we return to our Python component in `calendar.py` to tie this together.
|
Finally, we return to our Python component in `calendar.py` to tie this together.
|
||||||
|
@ -184,7 +185,7 @@ and [`css_file`](../../../reference/api#django_components.Component.css_file) at
|
||||||
from django_components import Component
|
from django_components import Component
|
||||||
|
|
||||||
class Calendar(Component):
|
class Calendar(Component):
|
||||||
template_name = "calendar.html"
|
template_file = "calendar.html"
|
||||||
js_file = "calendar.js" # <--- new
|
js_file = "calendar.js" # <--- new
|
||||||
css_file = "calendar.css" # <--- new
|
css_file = "calendar.css" # <--- new
|
||||||
|
|
||||||
|
@ -208,7 +209,6 @@ automatically embed the associated JS and CSS.
|
||||||
(e.g. `[your apps]/components` dir and `[project root]/components`)
|
(e.g. `[your apps]/components` dir and `[project root]/components`)
|
||||||
3. Relative to any of the directories defined by `STATICFILES_DIRS`.
|
3. Relative to any of the directories defined by `STATICFILES_DIRS`.
|
||||||
|
|
||||||
|
|
||||||
<!-- TODO: UPDATE AFTER AT LEAST ONE IMPLEMENTED
|
<!-- TODO: UPDATE AFTER AT LEAST ONE IMPLEMENTED
|
||||||
!!! info
|
!!! info
|
||||||
|
|
||||||
|
@ -243,7 +243,7 @@ with a few differences:
|
||||||
from django_components import Component
|
from django_components import Component
|
||||||
|
|
||||||
class Calendar(Component):
|
class Calendar(Component):
|
||||||
template_name = "calendar.html"
|
template_file = "calendar.html"
|
||||||
js_file = "calendar.js"
|
js_file = "calendar.js"
|
||||||
css_file = "calendar.css"
|
css_file = "calendar.css"
|
||||||
|
|
||||||
|
@ -273,7 +273,6 @@ class Calendar(Component):
|
||||||
and/or [`COMPONENTS.app_dirs`](../../reference/settings.md#django_components.app_settings.ComponentsSettings.app_dirs)
|
and/or [`COMPONENTS.app_dirs`](../../reference/settings.md#django_components.app_settings.ComponentsSettings.app_dirs)
|
||||||
(e.g. `[your apps]/components` dir and `[project root]/components`)
|
(e.g. `[your apps]/components` dir and `[project root]/components`)
|
||||||
|
|
||||||
|
|
||||||
!!! info
|
!!! info
|
||||||
|
|
||||||
The `Media` nested class is shaped based on [Django's Media class](https://docs.djangoproject.com/en/5.1/topics/forms/media/).
|
The `Media` nested class is shaped based on [Django's Media class](https://docs.djangoproject.com/en/5.1/topics/forms/media/).
|
||||||
|
@ -312,7 +311,7 @@ Additionally to `Media.js` applies that:
|
||||||
|
|
||||||
1. JS in `Media.js` is executed **before** the component's primary JS.
|
1. JS in `Media.js` is executed **before** the component's primary JS.
|
||||||
2. JS in `Media.js` is executed **in the same order** as it was defined.
|
2. JS in `Media.js` is executed **in the same order** as it was defined.
|
||||||
3. If there is multiple components that specify the same JS path or URL in `Media.js`,
|
3. If there is multiple components that specify the same JS path or URL in `Media.js`,
|
||||||
this JS will be still loaded and executed only once.
|
this JS will be still loaded and executed only once.
|
||||||
|
|
||||||
Putting all of this together, our `Calendar` component above would render HTML like so:
|
Putting all of this together, our `Calendar` component above would render HTML like so:
|
||||||
|
@ -322,8 +321,12 @@ Putting all of this together, our `Calendar` component above would render HTML l
|
||||||
<head>
|
<head>
|
||||||
...
|
...
|
||||||
<!-- CSS from Media.css -->
|
<!-- CSS from Media.css -->
|
||||||
<link href="/static/path/to/shared.css" media="all" rel="stylesheet">
|
<link href="/static/path/to/shared.css" media="all" rel="stylesheet" />
|
||||||
<link href="https://unpkg.com/tailwindcss@^2/dist/tailwind.min.css" media="all" rel="stylesheet">
|
<link
|
||||||
|
href="https://unpkg.com/tailwindcss@^2/dist/tailwind.min.css"
|
||||||
|
media="all"
|
||||||
|
rel="stylesheet"
|
||||||
|
/>
|
||||||
<!-- CSS from Component.css_file -->
|
<!-- CSS from Component.css_file -->
|
||||||
<style>
|
<style>
|
||||||
.calendar {
|
.calendar {
|
||||||
|
|
|
@ -8,7 +8,7 @@ our colleague - The calendar date needs to be shown on 3 different pages:
|
||||||
|
|
||||||
1. On one page, it needs to be shown as is
|
1. On one page, it needs to be shown as is
|
||||||
2. On the second, the date needs to be **bold**
|
2. On the second, the date needs to be **bold**
|
||||||
3. On the third, the date needs to be in *italics*
|
3. On the third, the date needs to be in _italics_
|
||||||
|
|
||||||
As a reminder, this is what the component's template looks like:
|
As a reminder, this is what the component's template looks like:
|
||||||
|
|
||||||
|
@ -43,7 +43,7 @@ In the example below we introduce two tags that work hand in hand to make this w
|
||||||
|
|
||||||
- `{% slot <name> %}`/`{% endslot %}`: Declares a new slot in the component template.
|
- `{% slot <name> %}`/`{% endslot %}`: Declares a new slot in the component template.
|
||||||
- `{% fill <name> %}`/`{% endfill %}`: (Used inside a [`{% component %}`](../../reference/template_tags.md#component)
|
- `{% fill <name> %}`/`{% endfill %}`: (Used inside a [`{% component %}`](../../reference/template_tags.md#component)
|
||||||
tag pair.) Fills a declared slot with the specified content.
|
tag pair.) Fills a declared slot with the specified content.
|
||||||
|
|
||||||
### 2. Add a slot tag
|
### 2. Add a slot tag
|
||||||
|
|
||||||
|
@ -122,7 +122,7 @@ Which will render as:
|
||||||
{% endcomponent %}
|
{% endcomponent %}
|
||||||
```
|
```
|
||||||
|
|
||||||
2. Implicitly as the [default slot](../fundamentals/slots.md#default-slot) (Omitting the
|
2. Implicitly as the [default slot](../fundamentals/slots.md#default-slot) (Omitting the
|
||||||
[`{% fill %}`](../../reference/template_tags.md#fill) tag)
|
[`{% fill %}`](../../reference/template_tags.md#fill) tag)
|
||||||
```htmldjango
|
```htmldjango
|
||||||
{% component "calendar" date="2024-12-13" %}
|
{% component "calendar" date="2024-12-13" %}
|
||||||
|
@ -177,7 +177,7 @@ def to_workweek_date(d: date):
|
||||||
|
|
||||||
@register("calendar")
|
@register("calendar")
|
||||||
class Calendar(Component):
|
class Calendar(Component):
|
||||||
template_name = "calendar.html"
|
template_file = "calendar.html"
|
||||||
...
|
...
|
||||||
def get_context_data(self, date: date, extra_class: str | None = None):
|
def get_context_data(self, date: date, extra_class: str | None = None):
|
||||||
workweek_date = to_workweek_date(date)
|
workweek_date = to_workweek_date(date)
|
||||||
|
@ -187,7 +187,7 @@ class Calendar(Component):
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
And the issue is that in our template, we used the `date` value that we used *as input*,
|
And the issue is that in our template, we used the `date` value that we used _as input_,
|
||||||
which is NOT the same as the `date` variable used inside Calendar's template.
|
which is NOT the same as the `date` variable used inside Calendar's template.
|
||||||
|
|
||||||
### 5. Adding data to slots
|
### 5. Adding data to slots
|
||||||
|
|
|
@ -31,7 +31,7 @@ from django_components import Component, register # <--- new
|
||||||
|
|
||||||
@register("calendar") # <--- new
|
@register("calendar") # <--- new
|
||||||
class Calendar(Component):
|
class Calendar(Component):
|
||||||
template_name = "calendar.html"
|
template_file = "calendar.html"
|
||||||
js_file = "calendar.js"
|
js_file = "calendar.js"
|
||||||
css_file = "calendar.css"
|
css_file = "calendar.css"
|
||||||
|
|
||||||
|
|
|
@ -23,7 +23,7 @@ from django_components import Component, register
|
||||||
|
|
||||||
@register("calendar")
|
@register("calendar")
|
||||||
class Calendar(Component):
|
class Calendar(Component):
|
||||||
template_name = "calendar.html"
|
template_file = "calendar.html"
|
||||||
...
|
...
|
||||||
def get_context_data(self):
|
def get_context_data(self):
|
||||||
return {
|
return {
|
||||||
|
@ -71,7 +71,7 @@ from django_components import Component, register
|
||||||
|
|
||||||
@register("calendar")
|
@register("calendar")
|
||||||
class Calendar(Component):
|
class Calendar(Component):
|
||||||
template_name = "calendar.html"
|
template_file = "calendar.html"
|
||||||
...
|
...
|
||||||
def get_context_data(self, date: date, extra_class: str | None = None):
|
def get_context_data(self, date: date, extra_class: str | None = None):
|
||||||
return {
|
return {
|
||||||
|
@ -135,7 +135,7 @@ We need to group the list items by size into following buckets by population:
|
||||||
|
|
||||||
- 0-10,000,000
|
- 0-10,000,000
|
||||||
- 10,000,001-20,000,000
|
- 10,000,001-20,000,000
|
||||||
- 20,000,001-30,000,000
|
- 20,000,001-30,000,000
|
||||||
- +30,000,001
|
- +30,000,001
|
||||||
|
|
||||||
So we want to end up with following data:
|
So we want to end up with following data:
|
||||||
|
@ -179,7 +179,7 @@ def group_by_pop(data):
|
||||||
|
|
||||||
@register("population_table")
|
@register("population_table")
|
||||||
class PopulationTable(Component):
|
class PopulationTable(Component):
|
||||||
template_name = "population_table.html"
|
template_file = "population_table.html"
|
||||||
|
|
||||||
def get_context_data(self, data):
|
def get_context_data(self, data):
|
||||||
return {
|
return {
|
||||||
|
@ -200,7 +200,7 @@ def to_workweek_date(d: date):
|
||||||
|
|
||||||
@register("calendar")
|
@register("calendar")
|
||||||
class Calendar(Component):
|
class Calendar(Component):
|
||||||
template_name = "calendar.html"
|
template_file = "calendar.html"
|
||||||
...
|
...
|
||||||
def get_context_data(self, date: date, extra_class: str | None = None):
|
def get_context_data(self, date: date, extra_class: str | None = None):
|
||||||
workweek_date = to_workweek_date(date) # <--- new
|
workweek_date = to_workweek_date(date) # <--- new
|
||||||
|
|
|
@ -15,7 +15,7 @@ A component in django-components can be as simple as a Django template and Pytho
|
||||||
from django_components import Component
|
from django_components import Component
|
||||||
|
|
||||||
class Calendar(Component):
|
class Calendar(Component):
|
||||||
template_name = "calendar.html"
|
template_file = "calendar.html"
|
||||||
```
|
```
|
||||||
|
|
||||||
Or a combination of Django template, Python, CSS, and Javascript:
|
Or a combination of Django template, Python, CSS, and Javascript:
|
||||||
|
@ -43,7 +43,7 @@ document.querySelector(".calendar").onclick = function () {
|
||||||
from django_components import Component
|
from django_components import Component
|
||||||
|
|
||||||
class Calendar(Component):
|
class Calendar(Component):
|
||||||
template_name = "calendar.html"
|
template_file = "calendar.html"
|
||||||
js_file = "calendar.js"
|
js_file = "calendar.js"
|
||||||
css_file = "calendar.css"
|
css_file = "calendar.css"
|
||||||
```
|
```
|
||||||
|
@ -80,7 +80,6 @@ class Calendar(Component):
|
||||||
[syntax highlighting](../../guides/setup/syntax_highlight.md) for better experience.
|
[syntax highlighting](../../guides/setup/syntax_highlight.md) for better experience.
|
||||||
However, autocompletion / intellisense does not work with syntax highlighting.
|
However, autocompletion / intellisense does not work with syntax highlighting.
|
||||||
|
|
||||||
|
|
||||||
We'll start by creating a component that defines only a Django template:
|
We'll start by creating a component that defines only a Django template:
|
||||||
|
|
||||||
### 1. Create project structure
|
### 1. Create project structure
|
||||||
|
@ -124,14 +123,14 @@ when creating an instance of this component.
|
||||||
In `calendar.py`, create a subclass of [Component](../../../reference/api#django_components.Component)
|
In `calendar.py`, create a subclass of [Component](../../../reference/api#django_components.Component)
|
||||||
to create a new component.
|
to create a new component.
|
||||||
|
|
||||||
To link the HTML template with our component, set [`template_name`](../../../reference/api#django_components.Component.template_name)
|
To link the HTML template with our component, set [`template_file`](../../../reference/api#django_components.Component.template_file)
|
||||||
to the name of the HTML file.
|
to the name of the HTML file.
|
||||||
|
|
||||||
```python title="[project root]/components/calendar/calendar.py"
|
```python title="[project root]/components/calendar/calendar.py"
|
||||||
from django_components import Component
|
from django_components import Component
|
||||||
|
|
||||||
class Calendar(Component):
|
class Calendar(Component):
|
||||||
template_name = "calendar.html"
|
template_file = "calendar.html"
|
||||||
```
|
```
|
||||||
|
|
||||||
!!! note
|
!!! note
|
||||||
|
@ -156,7 +155,7 @@ will become available within the template as variables, e.g. as `{{ date }}`.
|
||||||
from django_components import Component
|
from django_components import Component
|
||||||
|
|
||||||
class Calendar(Component):
|
class Calendar(Component):
|
||||||
template_name = "calendar.html"
|
template_file = "calendar.html"
|
||||||
|
|
||||||
def get_context_data(self):
|
def get_context_data(self):
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -65,7 +65,7 @@
|
||||||
```py
|
```py
|
||||||
@register("my_comp")
|
@register("my_comp")
|
||||||
class MyComp(Component):
|
class MyComp(Component):
|
||||||
template_name = "abc.html"
|
template_file = "abc.html"
|
||||||
```
|
```
|
||||||
|
|
||||||
Then:
|
Then:
|
||||||
|
@ -112,7 +112,7 @@
|
||||||
````py
|
````py
|
||||||
@register("my_comp")
|
@register("my_comp")
|
||||||
class MyComp(Component):
|
class MyComp(Component):
|
||||||
template_name = """
|
template_file = """
|
||||||
{% extends "abc.html" %}
|
{% extends "abc.html" %}
|
||||||
|
|
||||||
{% block inner %}
|
{% block inner %}
|
||||||
|
@ -129,7 +129,7 @@
|
||||||
```py
|
```py
|
||||||
@register("my_comp")
|
@register("my_comp")
|
||||||
class MyComp(Component):
|
class MyComp(Component):
|
||||||
template_name = """
|
template_file = """
|
||||||
{% extends "abc.html" %}
|
{% extends "abc.html" %}
|
||||||
|
|
||||||
{% load component_tags %}
|
{% load component_tags %}
|
||||||
|
|
|
@ -2,11 +2,11 @@
|
||||||
title: Welcome to Django Components
|
title: Welcome to Django Components
|
||||||
weight: 1
|
weight: 1
|
||||||
---
|
---
|
||||||
|
|
||||||
<img src="https://raw.githubusercontent.com/EmilStenstrom/django-components/master/logo/logo-black-on-white.svg" alt="django-components" style="max-width: 100%; background: white; color: black;">
|
<img src="https://raw.githubusercontent.com/EmilStenstrom/django-components/master/logo/logo-black-on-white.svg" alt="django-components" style="max-width: 100%; background: white; color: black;">
|
||||||
|
|
||||||
[](https://pypi.org/project/django-components/) [](https://pypi.org/project/django-components/) [](https://github.com/EmilStenstrom/django-components/blob/master/LICENSE/) [](https://pypistats.org/packages/django-components) [](https://github.com/EmilStenstrom/django-components/actions/workflows/tests.yml)
|
[](https://pypi.org/project/django-components/) [](https://pypi.org/project/django-components/) [](https://github.com/EmilStenstrom/django-components/blob/master/LICENSE/) [](https://pypistats.org/packages/django-components) [](https://github.com/EmilStenstrom/django-components/actions/workflows/tests.yml)
|
||||||
|
|
||||||
|
|
||||||
django-components introduces component-based architecture to Django's server-side rendering.
|
django-components introduces component-based architecture to Django's server-side rendering.
|
||||||
It combines Django's templating system with the modularity seen in modern frontend frameworks like Vue or React.
|
It combines Django's templating system with the modularity seen in modern frontend frameworks like Vue or React.
|
||||||
|
|
||||||
|
@ -22,7 +22,7 @@ A component in django-components can be as simple as a Django template and Pytho
|
||||||
from django_components import Component
|
from django_components import Component
|
||||||
|
|
||||||
class Calendar(Component):
|
class Calendar(Component):
|
||||||
template_name = "calendar.html"
|
template_file = "calendar.html"
|
||||||
```
|
```
|
||||||
|
|
||||||
Or a combination of Django template, Python, CSS, and Javascript:
|
Or a combination of Django template, Python, CSS, and Javascript:
|
||||||
|
@ -50,7 +50,7 @@ document.querySelector(".calendar").onclick = function () {
|
||||||
from django_components import Component
|
from django_components import Component
|
||||||
|
|
||||||
class Calendar(Component):
|
class Calendar(Component):
|
||||||
template_name = "calendar.html"
|
template_file = "calendar.html"
|
||||||
js_file = "calendar.js"
|
js_file = "calendar.js"
|
||||||
css_file = "calendar.css"
|
css_file = "calendar.css"
|
||||||
```
|
```
|
||||||
|
@ -100,13 +100,14 @@ Django-components can be particularly useful for larger Django projects that req
|
||||||
|
|
||||||
## Quickstart
|
## Quickstart
|
||||||
|
|
||||||
django-components lets you create reusable blocks of code needed to generate the front end code you need for a modern app.
|
django-components lets you create reusable blocks of code needed to generate the front end code you need for a modern app.
|
||||||
|
|
||||||
Define a component in `components/calendar/calendar.py` like this:
|
Define a component in `components/calendar/calendar.py` like this:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
@register("calendar")
|
@register("calendar")
|
||||||
class Calendar(Component):
|
class Calendar(Component):
|
||||||
template_name = "template.html"
|
template_file = "template.html"
|
||||||
|
|
||||||
def get_context_data(self, date):
|
def get_context_data(self, date):
|
||||||
return {"date": date}
|
return {"date": date}
|
||||||
|
|
|
@ -6,8 +6,8 @@ class Calendar(Component):
|
||||||
# Templates inside `[your apps]/components` dir and `[project root]/components` dir
|
# Templates inside `[your apps]/components` dir and `[project root]/components` dir
|
||||||
# will be automatically found.
|
# will be automatically found.
|
||||||
#
|
#
|
||||||
# `template_name` can be relative to dir where `calendar.py` is, or relative to COMPONENTS.dirs
|
# `template_file` can be relative to dir where `calendar.py` is, or relative to COMPONENTS.dirs
|
||||||
template_name = "calendar/calendar.html"
|
template_file = "calendar/calendar.html"
|
||||||
|
|
||||||
css_file = "calendar/calendar.css"
|
css_file = "calendar/calendar.css"
|
||||||
js_file = "calendar/calendar.js"
|
js_file = "calendar/calendar.js"
|
||||||
|
@ -31,8 +31,8 @@ class CalendarRelative(Component):
|
||||||
# Templates inside `[your apps]/components` dir and `[project root]/components` dir
|
# Templates inside `[your apps]/components` dir and `[project root]/components` dir
|
||||||
# will be automatically found.
|
# will be automatically found.
|
||||||
#
|
#
|
||||||
# `template_name` can be relative to dir where `calendar.py` is, or relative to COMPONENTS.dirs
|
# `template_file` can be relative to dir where `calendar.py` is, or relative to COMPONENTS.dirs
|
||||||
template_name = "calendar.html"
|
template_file = "calendar.html"
|
||||||
|
|
||||||
css_file = "calendar.css"
|
css_file = "calendar.css"
|
||||||
js_file = "calendar.js"
|
js_file = "calendar.js"
|
||||||
|
|
|
@ -6,8 +6,8 @@ class CalendarNested(Component):
|
||||||
# Templates inside `[your apps]/components` dir and `[project root]/components` dir
|
# Templates inside `[your apps]/components` dir and `[project root]/components` dir
|
||||||
# will be automatically found.
|
# will be automatically found.
|
||||||
#
|
#
|
||||||
# `template_name` can be relative to dir where `calendar.py` is, or relative to COMPONENTS.dirs
|
# `template_file` can be relative to dir where `calendar.py` is, or relative to COMPONENTS.dirs
|
||||||
template_name = "calendar.html"
|
template_file = "calendar.html"
|
||||||
|
|
||||||
css_file = "calendar.css"
|
css_file = "calendar.css"
|
||||||
js_file = "calendar.js"
|
js_file = "calendar.js"
|
||||||
|
|
|
@ -5,4 +5,4 @@ from django_components import Component, register
|
||||||
class Todo(Component):
|
class Todo(Component):
|
||||||
# Templates inside `[your apps]/components` dir and `[project root]/components` dir
|
# Templates inside `[your apps]/components` dir and `[project root]/components` dir
|
||||||
# will be automatically found.
|
# will be automatically found.
|
||||||
template_name = "todo/todo.html"
|
template_file = "todo/todo.html"
|
||||||
|
|
|
@ -158,14 +158,32 @@ class ComponentVars(NamedTuple):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
# Descriptor to pass getting/setting of `template_name` onto `template_file`
|
||||||
|
class ComponentTemplateNameDescriptor:
|
||||||
|
def __get__(self, instance: Optional["Component"], cls: Type["Component"]) -> Any:
|
||||||
|
obj = instance if instance is not None else cls
|
||||||
|
return obj.template_file # type: ignore[attr-defined]
|
||||||
|
|
||||||
|
def __set__(self, instance_or_cls: Union["Component", Type["Component"]], value: Any) -> None:
|
||||||
|
cls = instance_or_cls if isinstance(instance_or_cls, type) else instance_or_cls.__class__
|
||||||
|
cls.template_file = value
|
||||||
|
|
||||||
|
|
||||||
class ComponentMeta(ComponentMediaMeta):
|
class ComponentMeta(ComponentMediaMeta):
|
||||||
pass
|
def __new__(mcs, name: Any, bases: Tuple, attrs: Dict) -> Any:
|
||||||
|
# If user set `template_name` on the class, we instead set it to `template_file`,
|
||||||
|
# because we want `template_name` to be the descriptor that proxies to `template_file`.
|
||||||
|
if "template_name" in attrs:
|
||||||
|
attrs["template_file"] = attrs.pop("template_name")
|
||||||
|
attrs["template_name"] = ComponentTemplateNameDescriptor()
|
||||||
|
|
||||||
|
return super().__new__(mcs, name, bases, attrs)
|
||||||
|
|
||||||
|
|
||||||
# NOTE: We use metaclass to automatically define the HTTP methods as defined
|
# NOTE: We use metaclass to automatically define the HTTP methods as defined
|
||||||
# in `View.http_method_names`.
|
# in `View.http_method_names`.
|
||||||
class ComponentViewMeta(type):
|
class ComponentViewMeta(type):
|
||||||
def __new__(cls, name: str, bases: Any, dct: Dict) -> Any:
|
def __new__(mcs, name: str, bases: Any, dct: Dict) -> Any:
|
||||||
# Default implementation shared by all HTTP methods
|
# Default implementation shared by all HTTP methods
|
||||||
def create_handler(method: str) -> Callable:
|
def create_handler(method: str) -> Callable:
|
||||||
def handler(self, request: HttpRequest, *args: Any, **kwargs: Any): # type: ignore[no-untyped-def]
|
def handler(self, request: HttpRequest, *args: Any, **kwargs: Any): # type: ignore[no-untyped-def]
|
||||||
|
@ -179,7 +197,7 @@ class ComponentViewMeta(type):
|
||||||
if method_name not in dct:
|
if method_name not in dct:
|
||||||
dct[method_name] = create_handler(method_name)
|
dct[method_name] = create_handler(method_name)
|
||||||
|
|
||||||
return super().__new__(cls, name, bases, dct)
|
return super().__new__(mcs, name, bases, dct)
|
||||||
|
|
||||||
|
|
||||||
class ComponentView(View, metaclass=ComponentViewMeta):
|
class ComponentView(View, metaclass=ComponentViewMeta):
|
||||||
|
@ -205,14 +223,14 @@ class Component(
|
||||||
# PUBLIC API (Configurable by users)
|
# PUBLIC API (Configurable by users)
|
||||||
# #####################################
|
# #####################################
|
||||||
|
|
||||||
template_name: Optional[str] = None
|
template_file: Optional[str] = None
|
||||||
"""
|
"""
|
||||||
Filepath to the Django template associated with this component.
|
Filepath to the Django template associated with this component.
|
||||||
|
|
||||||
The filepath must be relative to either the file where the component class was defined,
|
The filepath must be relative to either the file where the component class was defined,
|
||||||
or one of the roots of `STATIFILES_DIRS`.
|
or one of the roots of `STATIFILES_DIRS`.
|
||||||
|
|
||||||
Only one of [`template_name`](../api#django_components.Component.template_name),
|
Only one of [`template_file`](../api#django_components.Component.template_file),
|
||||||
[`get_template_name`](../api#django_components.Component.get_template_name),
|
[`get_template_name`](../api#django_components.Component.get_template_name),
|
||||||
[`template`](../api#django_components.Component.template)
|
[`template`](../api#django_components.Component.template)
|
||||||
or [`get_template`](../api#django_components.Component.get_template) must be defined.
|
or [`get_template`](../api#django_components.Component.get_template) must be defined.
|
||||||
|
@ -221,13 +239,27 @@ class Component(
|
||||||
|
|
||||||
```py
|
```py
|
||||||
class MyComponent(Component):
|
class MyComponent(Component):
|
||||||
template_name = "path/to/template.html"
|
template_file = "path/to/template.html"
|
||||||
|
|
||||||
def get_context_data(self):
|
def get_context_data(self):
|
||||||
return {"name": "World"}
|
return {"name": "World"}
|
||||||
```
|
```
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
# NOTE: This attribute is managed by `ComponentTemplateNameDescriptor` that's set in the metaclass.
|
||||||
|
# But we still define it here for documenting and type hinting.
|
||||||
|
template_name: Optional[str]
|
||||||
|
"""
|
||||||
|
Alias for [`template_file`](../api#django_components.Component.template_file).
|
||||||
|
|
||||||
|
For historical reasons, django-components used `template_name` to align with Django's
|
||||||
|
[TemplateView](https://docs.djangoproject.com/en/5.1/ref/class-based-views/base/#django.views.generic.base.TemplateView).
|
||||||
|
|
||||||
|
`template_file` was introduced to align with `js/js_file` and `css/css_file`.
|
||||||
|
|
||||||
|
Setting and accessing this attribute is proxied to `template_file`.
|
||||||
|
"""
|
||||||
|
|
||||||
def get_template_name(self, context: Context) -> Optional[str]:
|
def get_template_name(self, context: Context) -> Optional[str]:
|
||||||
"""
|
"""
|
||||||
Filepath to the Django template associated with this component.
|
Filepath to the Django template associated with this component.
|
||||||
|
@ -235,7 +267,7 @@ class Component(
|
||||||
The filepath must be relative to either the file where the component class was defined,
|
The filepath must be relative to either the file where the component class was defined,
|
||||||
or one of the roots of `STATIFILES_DIRS`.
|
or one of the roots of `STATIFILES_DIRS`.
|
||||||
|
|
||||||
Only one of [`template_name`](../api#django_components.Component.template_name),
|
Only one of [`template_file`](../api#django_components.Component.template_file),
|
||||||
[`get_template_name`](../api#django_components.Component.get_template_name),
|
[`get_template_name`](../api#django_components.Component.get_template_name),
|
||||||
[`template`](../api#django_components.Component.template)
|
[`template`](../api#django_components.Component.template)
|
||||||
or [`get_template`](../api#django_components.Component.get_template) must be defined.
|
or [`get_template`](../api#django_components.Component.get_template) must be defined.
|
||||||
|
@ -246,7 +278,7 @@ class Component(
|
||||||
"""
|
"""
|
||||||
Inlined Django template associated with this component. Can be a plain string or a Template instance.
|
Inlined Django template associated with this component. Can be a plain string or a Template instance.
|
||||||
|
|
||||||
Only one of [`template_name`](../api#django_components.Component.template_name),
|
Only one of [`template_file`](../api#django_components.Component.template_file),
|
||||||
[`get_template_name`](../api#django_components.Component.get_template_name),
|
[`get_template_name`](../api#django_components.Component.get_template_name),
|
||||||
[`template`](../api#django_components.Component.template)
|
[`template`](../api#django_components.Component.template)
|
||||||
or [`get_template`](../api#django_components.Component.get_template) must be defined.
|
or [`get_template`](../api#django_components.Component.get_template) must be defined.
|
||||||
|
@ -266,7 +298,7 @@ class Component(
|
||||||
"""
|
"""
|
||||||
Inlined Django template associated with this component. Can be a plain string or a Template instance.
|
Inlined Django template associated with this component. Can be a plain string or a Template instance.
|
||||||
|
|
||||||
Only one of [`template_name`](../api#django_components.Component.template_name),
|
Only one of [`template_file`](../api#django_components.Component.template_file),
|
||||||
[`get_template_name`](../api#django_components.Component.get_template_name),
|
[`get_template_name`](../api#django_components.Component.get_template_name),
|
||||||
[`template`](../api#django_components.Component.template)
|
[`template`](../api#django_components.Component.template)
|
||||||
or [`get_template`](../api#django_components.Component.get_template) must be defined.
|
or [`get_template`](../api#django_components.Component.get_template) must be defined.
|
||||||
|
@ -596,21 +628,21 @@ class Component(
|
||||||
|
|
||||||
return ctx.is_filled
|
return ctx.is_filled
|
||||||
|
|
||||||
# NOTE: When the template is taken from a file (AKA specified via `template_name`),
|
# NOTE: When the template is taken from a file (AKA specified via `template_file`),
|
||||||
# then we leverage Django's template caching. This means that the same instance
|
# then we leverage Django's template caching. This means that the same instance
|
||||||
# of Template is reused. This is important to keep in mind, because the implication
|
# of Template is reused. This is important to keep in mind, because the implication
|
||||||
# is that we should treat Templates AND their nodelists as IMMUTABLE.
|
# is that we should treat Templates AND their nodelists as IMMUTABLE.
|
||||||
def _get_template(self, context: Context) -> Template:
|
def _get_template(self, context: Context) -> Template:
|
||||||
# Resolve template name
|
# Resolve template name
|
||||||
template_name = self.template_name
|
template_file = self.template_file
|
||||||
if self.template_name is not None:
|
if self.template_file is not None:
|
||||||
if self.get_template_name(context) is not None:
|
if self.get_template_name(context) is not None:
|
||||||
raise ImproperlyConfigured(
|
raise ImproperlyConfigured(
|
||||||
"Received non-null value from both 'template_name' and 'get_template_name' in"
|
"Received non-null value from both 'template_file' and 'get_template_name' in"
|
||||||
f" Component {type(self).__name__}. Only one of the two must be set."
|
f" Component {type(self).__name__}. Only one of the two must be set."
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
template_name = self.get_template_name(context)
|
template_file = self.get_template_name(context)
|
||||||
|
|
||||||
# Resolve template str
|
# Resolve template str
|
||||||
template_input = self.template
|
template_input = self.template
|
||||||
|
@ -625,14 +657,14 @@ class Component(
|
||||||
template_getter = getattr(self, "get_template_string", self.get_template)
|
template_getter = getattr(self, "get_template_string", self.get_template)
|
||||||
template_input = template_getter(context)
|
template_input = template_getter(context)
|
||||||
|
|
||||||
if template_name is not None and template_input is not None:
|
if template_file is not None and template_input is not None:
|
||||||
raise ImproperlyConfigured(
|
raise ImproperlyConfigured(
|
||||||
f"Received both 'template_name' and 'template' in Component {type(self).__name__}."
|
f"Received both 'template_file' and 'template' in Component {type(self).__name__}."
|
||||||
" Only one of the two must be set."
|
" Only one of the two must be set."
|
||||||
)
|
)
|
||||||
|
|
||||||
if template_name is not None:
|
if template_file is not None:
|
||||||
return get_template(template_name).template
|
return get_template(template_file).template
|
||||||
|
|
||||||
elif template_input is not None:
|
elif template_input is not None:
|
||||||
# We got template string, so we convert it to Template
|
# We got template string, so we convert it to Template
|
||||||
|
@ -644,7 +676,7 @@ class Component(
|
||||||
return template
|
return template
|
||||||
|
|
||||||
raise ImproperlyConfigured(
|
raise ImproperlyConfigured(
|
||||||
f"Either 'template_name' or 'template' must be set for Component {type(self).__name__}."
|
f"Either 'template_file' or 'template' must be set for Component {type(self).__name__}."
|
||||||
)
|
)
|
||||||
|
|
||||||
def inject(self, key: str, default: Optional[Any] = None) -> Any:
|
def inject(self, key: str, default: Optional[Any] = None) -> Any:
|
||||||
|
|
|
@ -18,7 +18,7 @@ if TYPE_CHECKING:
|
||||||
|
|
||||||
|
|
||||||
# These are all the attributes that are handled by ComponentMedia and lazily-resolved
|
# These are all the attributes that are handled by ComponentMedia and lazily-resolved
|
||||||
COMP_MEDIA_LAZY_ATTRS = ("media", "template", "template_name", "js", "js_file", "css", "css_file")
|
COMP_MEDIA_LAZY_ATTRS = ("media", "template", "template_file", "js", "js_file", "css", "css_file")
|
||||||
|
|
||||||
|
|
||||||
ComponentMediaInputPath = Union[
|
ComponentMediaInputPath = Union[
|
||||||
|
@ -176,7 +176,7 @@ class ComponentMedia:
|
||||||
media: Optional[MediaCls] = None
|
media: Optional[MediaCls] = None
|
||||||
media_class: Type[MediaCls] = MediaCls
|
media_class: Type[MediaCls] = MediaCls
|
||||||
template: Optional[str] = None
|
template: Optional[str] = None
|
||||||
template_name: Optional[str] = None
|
template_file: Optional[str] = None
|
||||||
js: Optional[str] = None
|
js: Optional[str] = None
|
||||||
js_file: Optional[str] = None
|
js_file: Optional[str] = None
|
||||||
css: Optional[str] = None
|
css: Optional[str] = None
|
||||||
|
@ -185,9 +185,9 @@ class ComponentMedia:
|
||||||
|
|
||||||
# This metaclass is all about one thing - lazily resolving the media files.
|
# This metaclass is all about one thing - lazily resolving the media files.
|
||||||
#
|
#
|
||||||
# All the CSS/JS/HTML associated with a component - e.g. the `js`, `js_file`, `template_name` or `Media` class,
|
# All the CSS/JS/HTML associated with a component - e.g. the `js`, `js_file`, `template_file` or `Media` class,
|
||||||
# are all class attributes. And some of these attributes need to be resolved, e.g. to find the files
|
# are all class attributes. And some of these attributes need to be resolved, e.g. to find the files
|
||||||
# that `js_file`, `css_file` and `template_name` point to.
|
# that `js_file`, `css_file` and `template_file` point to.
|
||||||
#
|
#
|
||||||
# Some of the resolutions we need to do is:
|
# Some of the resolutions we need to do is:
|
||||||
# - Component's HTML/JS/CSS files can be defined as relative to the component class file. So for each file,
|
# - Component's HTML/JS/CSS files can be defined as relative to the component class file. So for each file,
|
||||||
|
@ -240,7 +240,14 @@ class ComponentMediaMeta(type):
|
||||||
" already resolved. This may lead to unexpected behavior."
|
" already resolved. This may lead to unexpected behavior."
|
||||||
)
|
)
|
||||||
|
|
||||||
super().__setattr__(name, value)
|
# NOTE: When a metaclass specifies a `__setattr__` method, this overrides the normal behavior of
|
||||||
|
# setting an attribute on the class with Descriptors. So we need to call the normal behavior explicitly.
|
||||||
|
# NOTE 2: `__dict__` is used to access the class attributes directly, without triggering the descriptors.
|
||||||
|
desc = cls.__dict__.get(name, None)
|
||||||
|
if hasattr(desc, "__set__"):
|
||||||
|
desc.__set__(cls, value)
|
||||||
|
else:
|
||||||
|
super().__setattr__(name, value)
|
||||||
|
|
||||||
|
|
||||||
# This sets up the lazy resolution of the media attributes.
|
# This sets up the lazy resolution of the media attributes.
|
||||||
|
@ -254,7 +261,7 @@ def _setup_lazy_media_resolve(comp_cls: Type["Component"], attrs: Dict[str, Any]
|
||||||
media=attrs.get("media", None),
|
media=attrs.get("media", None),
|
||||||
media_class=attrs.get("media_class", None),
|
media_class=attrs.get("media_class", None),
|
||||||
template=attrs.get("template", None),
|
template=attrs.get("template", None),
|
||||||
template_name=attrs.get("template_name", None),
|
template_file=attrs.get("template_file", None),
|
||||||
js=attrs.get("js", None),
|
js=attrs.get("js", None),
|
||||||
js_file=attrs.get("js_file", None),
|
js_file=attrs.get("js_file", None),
|
||||||
css=attrs.get("css", None),
|
css=attrs.get("css", None),
|
||||||
|
@ -292,8 +299,8 @@ def _setup_lazy_media_resolve(comp_cls: Type["Component"], attrs: Dict[str, Any]
|
||||||
continue
|
continue
|
||||||
else:
|
else:
|
||||||
return value
|
return value
|
||||||
if attr in ("template", "template_name"):
|
if attr in ("template", "template_file"):
|
||||||
if check_pair_empty("template", "template_name"):
|
if check_pair_empty("template", "template_file"):
|
||||||
continue
|
continue
|
||||||
else:
|
else:
|
||||||
return value
|
return value
|
||||||
|
@ -544,7 +551,7 @@ def _resolve_component_relative_files(
|
||||||
# HTML/JS/CSS files, just skip.
|
# HTML/JS/CSS files, just skip.
|
||||||
will_resolve_files = False
|
will_resolve_files = False
|
||||||
if (
|
if (
|
||||||
getattr(comp_media, "template_name", None)
|
getattr(comp_media, "template_file", None)
|
||||||
or getattr(comp_media, "js_file", None)
|
or getattr(comp_media, "js_file", None)
|
||||||
or getattr(comp_media, "css_file", None)
|
or getattr(comp_media, "css_file", None)
|
||||||
):
|
):
|
||||||
|
@ -609,8 +616,8 @@ def _resolve_component_relative_files(
|
||||||
return filepath
|
return filepath
|
||||||
|
|
||||||
# Check if template name is a local file or not
|
# Check if template name is a local file or not
|
||||||
if getattr(comp_media, "template_name", None):
|
if getattr(comp_media, "template_file", None):
|
||||||
comp_media.template_name = resolve_media_file(comp_media.template_name)
|
comp_media.template_file = resolve_media_file(comp_media.template_file)
|
||||||
if getattr(comp_media, "js_file", None):
|
if getattr(comp_media, "js_file", None):
|
||||||
comp_media.js_file = resolve_media_file(comp_media.js_file)
|
comp_media.js_file = resolve_media_file(comp_media.js_file)
|
||||||
if getattr(comp_media, "css_file", None):
|
if getattr(comp_media, "css_file", None):
|
||||||
|
|
|
@ -197,7 +197,7 @@ class Command(BaseCommand):
|
||||||
|
|
||||||
@register("{name}")
|
@register("{name}")
|
||||||
class {name.capitalize()}(Component):
|
class {name.capitalize()}(Component):
|
||||||
template_name = "{name}/{template_filename}"
|
template_file = "{name}/{template_filename}"
|
||||||
|
|
||||||
def get_context_data(self, value):
|
def get_context_data(self, value):
|
||||||
return {{
|
return {{
|
||||||
|
|
|
@ -7,7 +7,7 @@ from django_components import Component, register
|
||||||
|
|
||||||
@register("multi_file_component")
|
@register("multi_file_component")
|
||||||
class MultFileComponent(Component):
|
class MultFileComponent(Component):
|
||||||
template_name = "multi_file/multi_file.html"
|
template_file = "multi_file/multi_file.html"
|
||||||
|
|
||||||
def post(self, request, *args, **kwargs) -> HttpResponse:
|
def post(self, request, *args, **kwargs) -> HttpResponse:
|
||||||
variable = request.POST.get("variable")
|
variable = request.POST.get("variable")
|
||||||
|
|
|
@ -7,7 +7,7 @@ from django_components import Component, register
|
||||||
|
|
||||||
@register("relative_file_component")
|
@register("relative_file_component")
|
||||||
class RelativeFileComponent(Component):
|
class RelativeFileComponent(Component):
|
||||||
template_name = "relative_file.html"
|
template_file = "relative_file.html"
|
||||||
|
|
||||||
class Media:
|
class Media:
|
||||||
js = "relative_file.js"
|
js = "relative_file.js"
|
||||||
|
|
|
@ -26,7 +26,7 @@ class PathObj:
|
||||||
|
|
||||||
@register("relative_file_pathobj_component")
|
@register("relative_file_pathobj_component")
|
||||||
class RelativeFileWithPathObjComponent(Component):
|
class RelativeFileWithPathObjComponent(Component):
|
||||||
template_name = "relative_file_pathobj.html"
|
template_file = "relative_file_pathobj.html"
|
||||||
|
|
||||||
class Media:
|
class Media:
|
||||||
js = PathObj("relative_file_pathobj.js")
|
js = PathObj("relative_file_pathobj.js")
|
||||||
|
|
|
@ -6,7 +6,7 @@ from django_components import Component, register
|
||||||
# Used for testing the staticfiles finder in `test_staticfiles.py`
|
# Used for testing the staticfiles finder in `test_staticfiles.py`
|
||||||
@register("staticfiles_component")
|
@register("staticfiles_component")
|
||||||
class RelativeFileWithPathObjComponent(Component):
|
class RelativeFileWithPathObjComponent(Component):
|
||||||
template_name = "staticfiles.html"
|
template_file = "staticfiles.html"
|
||||||
|
|
||||||
class Media:
|
class Media:
|
||||||
js = "staticfiles.js"
|
js = "staticfiles.js"
|
||||||
|
|
|
@ -6,7 +6,7 @@ from django_components import Component, register
|
||||||
# Used for testing the template_loader
|
# Used for testing the template_loader
|
||||||
@register("app_lvl_comp")
|
@register("app_lvl_comp")
|
||||||
class AppLvlCompComponent(Component):
|
class AppLvlCompComponent(Component):
|
||||||
template_name: Optional[str] = "app_lvl_comp.html"
|
template_file: Optional[str] = "app_lvl_comp.html"
|
||||||
js_file = "app_lvl_comp.js"
|
js_file = "app_lvl_comp.js"
|
||||||
css_file = "app_lvl_comp.css"
|
css_file = "app_lvl_comp.css"
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,7 @@ from django_components import Component, register
|
||||||
# Used for testing the template_loader
|
# Used for testing the template_loader
|
||||||
@register("custom_app_lvl_comp")
|
@register("custom_app_lvl_comp")
|
||||||
class AppLvlCompComponent(Component):
|
class AppLvlCompComponent(Component):
|
||||||
template_name = "app_lvl_comp.html"
|
template_file = "app_lvl_comp.html"
|
||||||
|
|
||||||
class Media:
|
class Media:
|
||||||
js = "app_lvl_comp.js"
|
js = "app_lvl_comp.js"
|
||||||
|
|
|
@ -206,9 +206,9 @@ class ComponentTest(BaseTestCase):
|
||||||
)
|
)
|
||||||
|
|
||||||
@parametrize_context_behavior(["django", "isolated"])
|
@parametrize_context_behavior(["django", "isolated"])
|
||||||
def test_template_name_static(self):
|
def test_template_file_static(self):
|
||||||
class SimpleComponent(Component):
|
class SimpleComponent(Component):
|
||||||
template_name = "simple_template.html"
|
template_file = "simple_template.html"
|
||||||
|
|
||||||
def get_context_data(self, variable=None):
|
def get_context_data(self, variable=None):
|
||||||
return {
|
return {
|
||||||
|
@ -228,7 +228,58 @@ class ComponentTest(BaseTestCase):
|
||||||
)
|
)
|
||||||
|
|
||||||
@parametrize_context_behavior(["django", "isolated"])
|
@parametrize_context_behavior(["django", "isolated"])
|
||||||
def test_template_name_dynamic(self):
|
def test_template_file_static__compat(self):
|
||||||
|
class SimpleComponent(Component):
|
||||||
|
template_name = "simple_template.html"
|
||||||
|
|
||||||
|
def get_context_data(self, variable=None):
|
||||||
|
return {
|
||||||
|
"variable": variable,
|
||||||
|
}
|
||||||
|
|
||||||
|
class Media:
|
||||||
|
css = "style.css"
|
||||||
|
js = "script.js"
|
||||||
|
|
||||||
|
self.assertEqual(SimpleComponent.template_name, "simple_template.html")
|
||||||
|
self.assertEqual(SimpleComponent.template_file, "simple_template.html")
|
||||||
|
|
||||||
|
SimpleComponent.template_name = "other_template.html"
|
||||||
|
self.assertEqual(SimpleComponent.template_name, "other_template.html")
|
||||||
|
self.assertEqual(SimpleComponent.template_file, "other_template.html")
|
||||||
|
|
||||||
|
SimpleComponent.template_name = "simple_template.html"
|
||||||
|
rendered = SimpleComponent.render(kwargs={"variable": "test"})
|
||||||
|
self.assertHTMLEqual(
|
||||||
|
rendered,
|
||||||
|
"""
|
||||||
|
Variable: <strong data-djc-id-a1bc3e>test</strong>
|
||||||
|
""",
|
||||||
|
)
|
||||||
|
|
||||||
|
comp = SimpleComponent()
|
||||||
|
self.assertEqual(comp.template_name, "simple_template.html")
|
||||||
|
self.assertEqual(comp.template_file, "simple_template.html")
|
||||||
|
|
||||||
|
# NOTE: Setting `template_file` on INSTANCE is not supported, as users should work
|
||||||
|
# with classes and not instances. This is tested for completeness.
|
||||||
|
comp.template_name = "other_template_2.html"
|
||||||
|
self.assertEqual(comp.template_name, "other_template_2.html")
|
||||||
|
self.assertEqual(comp.template_file, "other_template_2.html")
|
||||||
|
self.assertEqual(SimpleComponent.template_name, "other_template_2.html")
|
||||||
|
self.assertEqual(SimpleComponent.template_file, "other_template_2.html")
|
||||||
|
|
||||||
|
SimpleComponent.template_name = "simple_template.html"
|
||||||
|
rendered = comp.render(kwargs={"variable": "test"})
|
||||||
|
self.assertHTMLEqual(
|
||||||
|
rendered,
|
||||||
|
"""
|
||||||
|
Variable: <strong data-djc-id-a1bc3f>test</strong>
|
||||||
|
""",
|
||||||
|
)
|
||||||
|
|
||||||
|
@parametrize_context_behavior(["django", "isolated"])
|
||||||
|
def test_template_file_dynamic(self):
|
||||||
class SvgComponent(Component):
|
class SvgComponent(Component):
|
||||||
def get_context_data(self, name, css_class="", title="", **attrs):
|
def get_context_data(self, name, css_class="", title="", **attrs):
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -31,7 +31,7 @@ class MainMediaTest(BaseTestCase):
|
||||||
|
|
||||||
def test_html_filepath(self):
|
def test_html_filepath(self):
|
||||||
class Test(Component):
|
class Test(Component):
|
||||||
template_name = "simple_template.html"
|
template_file = "simple_template.html"
|
||||||
|
|
||||||
rendered = Test.render(context={"variable": "test"})
|
rendered = Test.render(context={"variable": "test"})
|
||||||
|
|
||||||
|
@ -86,7 +86,7 @@ class MainMediaTest(BaseTestCase):
|
||||||
from tests.test_app.components.app_lvl_comp.app_lvl_comp import AppLvlCompComponent
|
from tests.test_app.components.app_lvl_comp.app_lvl_comp import AppLvlCompComponent
|
||||||
|
|
||||||
class TestComponent(AppLvlCompComponent):
|
class TestComponent(AppLvlCompComponent):
|
||||||
template_name = None
|
template_file = None
|
||||||
template = """
|
template = """
|
||||||
{% load component_tags %}
|
{% load component_tags %}
|
||||||
{% component_js_dependencies %}
|
{% component_js_dependencies %}
|
||||||
|
@ -167,7 +167,7 @@ class MainMediaTest(BaseTestCase):
|
||||||
from tests.test_app.components.app_lvl_comp.app_lvl_comp import AppLvlCompComponent
|
from tests.test_app.components.app_lvl_comp.app_lvl_comp import AppLvlCompComponent
|
||||||
|
|
||||||
class TestComponent(AppLvlCompComponent):
|
class TestComponent(AppLvlCompComponent):
|
||||||
template_name = None
|
template_file = None
|
||||||
template = """
|
template = """
|
||||||
{% load component_tags %}
|
{% load component_tags %}
|
||||||
{% component_js_dependencies %}
|
{% component_js_dependencies %}
|
||||||
|
|
|
@ -13,7 +13,7 @@ setup_test_config({"autodiscover": False})
|
||||||
|
|
||||||
|
|
||||||
class SlottedComponent(Component):
|
class SlottedComponent(Component):
|
||||||
template_name = "slotted_template.html"
|
template_file = "slotted_template.html"
|
||||||
|
|
||||||
|
|
||||||
#######################
|
#######################
|
||||||
|
@ -42,7 +42,7 @@ class TemplateInstrumentationTest(BaseTestCase):
|
||||||
|
|
||||||
@register("inner_component")
|
@register("inner_component")
|
||||||
class SimpleComponent(Component):
|
class SimpleComponent(Component):
|
||||||
template_name = "simple_template.html"
|
template_file = "simple_template.html"
|
||||||
|
|
||||||
def get_context_data(self, variable, variable2="default"):
|
def get_context_data(self, variable, variable2="default"):
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -9,7 +9,7 @@ setup_test_config({"autodiscover": False})
|
||||||
|
|
||||||
|
|
||||||
class SlottedComponent(Component):
|
class SlottedComponent(Component):
|
||||||
template_name = "slotted_template.html"
|
template_file = "slotted_template.html"
|
||||||
|
|
||||||
|
|
||||||
class SlottedComponentWithContext(Component):
|
class SlottedComponentWithContext(Component):
|
||||||
|
|
|
@ -11,11 +11,11 @@ setup_test_config({"autodiscover": False})
|
||||||
|
|
||||||
|
|
||||||
class SlottedComponent(Component):
|
class SlottedComponent(Component):
|
||||||
template_name = "slotted_template.html"
|
template_file = "slotted_template.html"
|
||||||
|
|
||||||
|
|
||||||
class BlockedAndSlottedComponent(Component):
|
class BlockedAndSlottedComponent(Component):
|
||||||
template_name = "blocked_and_slotted_template.html"
|
template_file = "blocked_and_slotted_template.html"
|
||||||
|
|
||||||
|
|
||||||
#######################
|
#######################
|
||||||
|
@ -418,7 +418,7 @@ class ExtendsCompatTests(BaseTestCase):
|
||||||
|
|
||||||
@register("extended_component")
|
@register("extended_component")
|
||||||
class _ExtendedComponent(Component):
|
class _ExtendedComponent(Component):
|
||||||
template_name = "included.html"
|
template_file = "included.html"
|
||||||
|
|
||||||
template: types.django_html = """
|
template: types.django_html = """
|
||||||
{% extends 'block.html' %}
|
{% extends 'block.html' %}
|
||||||
|
@ -587,7 +587,7 @@ class ExtendsCompatTests(BaseTestCase):
|
||||||
|
|
||||||
@register("block_in_component_parent")
|
@register("block_in_component_parent")
|
||||||
class BlockInCompParent(Component):
|
class BlockInCompParent(Component):
|
||||||
template_name = "block_in_component_parent.html"
|
template_file = "block_in_component_parent.html"
|
||||||
|
|
||||||
template: types.django_html = """
|
template: types.django_html = """
|
||||||
{% load component_tags %}
|
{% load component_tags %}
|
||||||
|
@ -620,7 +620,7 @@ class ExtendsCompatTests(BaseTestCase):
|
||||||
|
|
||||||
@register("block_inside_slot_v1")
|
@register("block_inside_slot_v1")
|
||||||
class BlockInSlotInComponent(Component):
|
class BlockInSlotInComponent(Component):
|
||||||
template_name = "block_in_slot_in_component.html"
|
template_file = "block_in_slot_in_component.html"
|
||||||
|
|
||||||
template: types.django_html = """
|
template: types.django_html = """
|
||||||
{% load component_tags %}
|
{% load component_tags %}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue