mirror of
https://github.com/django-components/django-components.git
synced 2025-08-17 12:40:15 +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
|
||||
|
||||
- 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:
|
||||
|
||||
- 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
|
||||
|
||||
class Calendar(Component):
|
||||
template_name = "calendar.html"
|
||||
template_file = "calendar.html"
|
||||
```
|
||||
|
||||
Or a combination of Django template, Python, CSS, and Javascript:
|
||||
|
@ -46,7 +46,7 @@ document.querySelector(".calendar").onclick = function () {
|
|||
from django_components import Component
|
||||
|
||||
class Calendar(Component):
|
||||
template_name = "calendar.html"
|
||||
template_file = "calendar.html"
|
||||
js_file = "calendar.js"
|
||||
css_file = "calendar.css"
|
||||
```
|
||||
|
@ -77,7 +77,6 @@ class Calendar(Component):
|
|||
"""
|
||||
```
|
||||
|
||||
|
||||
## Features
|
||||
|
||||
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
|
||||
|
||||
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:
|
||||
|
||||
```python
|
||||
@register("calendar")
|
||||
class Calendar(Component):
|
||||
template_name = "template.html"
|
||||
template_file = "template.html"
|
||||
|
||||
def get_context_data(self, date):
|
||||
return {"date": date}
|
||||
|
|
|
@ -14,7 +14,7 @@ from django_components import Component, register
|
|||
|
||||
@register("calendar")
|
||||
class Calendar(Component):
|
||||
template_name = "template.html"
|
||||
template_file = "template.html"
|
||||
|
||||
# This component takes one parameter, a date string to show in the template
|
||||
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.
|
||||
|
||||
The registry accepts these settings:
|
||||
|
||||
- `context_behavior`
|
||||
- `tag_formatter`
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@ HTML / JS / CSS with a component:
|
|||
[`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_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.js_file`](../../reference/api.md#django_components.Component.js_file) to define the main HTML / CSS / JS
|
||||
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 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.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
|
||||
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)
|
||||
and
|
||||
[`Component.css_file`](../../reference/api.md#django_components.Component.css_file) respectively:
|
||||
|
@ -61,7 +61,7 @@ from django_components import Component, register
|
|||
|
||||
@register("calendar")
|
||||
class Calendar(Component):
|
||||
template_name = "template.html"
|
||||
template_file = "template.html"
|
||||
css_file = "style.css"
|
||||
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
|
||||
[`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).
|
||||
|
||||
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")
|
||||
class Calendar(Component):
|
||||
template_name = "calendar/template.html"
|
||||
template_file = "calendar/template.html"
|
||||
css_file = "calendar/style.css"
|
||||
js_file = "calendar/script.js"
|
||||
```
|
||||
|
@ -94,59 +94,59 @@ class Calendar(Component):
|
|||
|
||||
**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.
|
||||
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)
|
||||
or
|
||||
or
|
||||
[`COMPONENTS.app_dirs`](../../reference/settings.md#django_components.app_settings.ComponentsSettings.app_dirs).
|
||||
|
||||
**Example:**
|
||||
|
||||
```py title="[root]/components/mytable/mytable.py"
|
||||
class MyTable(Component):
|
||||
template_name = "mytable.html"
|
||||
template_file = "mytable.html"
|
||||
```
|
||||
|
||||
1. Component `MyTable` is defined in file `[root]/components/mytable/mytable.py`.
|
||||
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`.
|
||||
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.
|
||||
6. django-components searches `COMPONENTS.dirs` and `COMPONENTS.app_dirs` for a first
|
||||
directory that contains `[root]/components/mytable/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.
|
||||
|
||||
## 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).
|
||||
|
||||
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):
|
||||
|
||||
- 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.
|
||||
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),
|
||||
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()`.
|
||||
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()`.
|
||||
|
||||
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)).
|
||||
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)).
|
||||
[`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. 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
|
||||
class MyTable(Component):
|
||||
|
@ -268,7 +268,7 @@ class ModuleJsPath:
|
|||
|
||||
@register("calendar")
|
||||
class Calendar(Component):
|
||||
template_name = "calendar/template.html"
|
||||
template_file = "calendar/template.html"
|
||||
|
||||
def get_context_data(self, date):
|
||||
return {
|
||||
|
@ -314,7 +314,7 @@ class MyMedia(Media):
|
|||
|
||||
@register("calendar")
|
||||
class Calendar(Component):
|
||||
template_name = "calendar/template.html"
|
||||
template_file = "calendar/template.html"
|
||||
css_file = "calendar/style.css"
|
||||
js_file = "calendar/script.js"
|
||||
|
||||
|
@ -331,27 +331,28 @@ class Calendar(Component):
|
|||
Component's HTML / CSS / JS is resolved and loaded lazily.
|
||||
|
||||
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),
|
||||
[`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_name`](../../reference/api.md#django_components.Component.template_name),
|
||||
[`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),
|
||||
[`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_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)
|
||||
- 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)
|
||||
|
@ -359,7 +360,7 @@ on the Component class:
|
|||
will be available under [`Component.js`](../../reference/api.md#django_components.Component.js)
|
||||
|
||||
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),
|
||||
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.
|
||||
|
@ -371,7 +372,7 @@ And the same applies for JS and CSS.
|
|||
# are not yet loaded!
|
||||
@register("calendar")
|
||||
class Calendar(Component):
|
||||
template_name = "calendar/template.html"
|
||||
template_file = "calendar/template.html"
|
||||
css_file = "calendar/style.css"
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
|
|
|
@ -3,10 +3,13 @@ title: Single-file components
|
|||
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"
|
||||
# In a file called [project root]/components/calendar.py
|
||||
from django_components import Component, register, types
|
||||
|
||||
@register("calendar")
|
||||
|
@ -17,18 +20,27 @@ class Calendar(Component):
|
|||
}
|
||||
|
||||
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 = """
|
||||
.calendar-component { width: 200px; background: pink; }
|
||||
.calendar-component span { font-weight: bold; }
|
||||
.calendar {
|
||||
width: 200px;
|
||||
background: pink;
|
||||
}
|
||||
.calendar span {
|
||||
font-weight: bold;
|
||||
}
|
||||
"""
|
||||
|
||||
js: types.js = """
|
||||
(function(){
|
||||
if (document.querySelector(".calendar-component")) {
|
||||
document.querySelector(".calendar-component").onclick = function(){ alert("Clicked calendar!"); };
|
||||
if (document.querySelector(".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.
|
||||
|
||||
<!-- TODO: UPDATE AFTER SCOPED CSS ADDED -->
|
||||
|
||||
!!! note
|
||||
|
||||
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.
|
||||
|
||||
<!-- TODO: UPDATE AFTER FUNCTIONS WRAPPED -->
|
||||
|
||||
!!! note
|
||||
|
||||
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>
|
||||
```
|
||||
|
||||
|
||||
### 4. Link JS and CSS to a component
|
||||
|
||||
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
|
||||
|
||||
class Calendar(Component):
|
||||
template_name = "calendar.html"
|
||||
template_file = "calendar.html"
|
||||
js_file = "calendar.js" # <--- 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`)
|
||||
3. Relative to any of the directories defined by `STATICFILES_DIRS`.
|
||||
|
||||
|
||||
<!-- TODO: UPDATE AFTER AT LEAST ONE IMPLEMENTED
|
||||
!!! info
|
||||
|
||||
|
@ -243,7 +243,7 @@ with a few differences:
|
|||
from django_components import Component
|
||||
|
||||
class Calendar(Component):
|
||||
template_name = "calendar.html"
|
||||
template_file = "calendar.html"
|
||||
js_file = "calendar.js"
|
||||
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)
|
||||
(e.g. `[your apps]/components` dir and `[project root]/components`)
|
||||
|
||||
|
||||
!!! info
|
||||
|
||||
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.
|
||||
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.
|
||||
|
||||
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>
|
||||
...
|
||||
<!-- CSS from Media.css -->
|
||||
<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="/static/path/to/shared.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 -->
|
||||
<style>
|
||||
.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
|
||||
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:
|
||||
|
||||
|
@ -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.
|
||||
- `{% 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
|
||||
|
||||
|
@ -122,7 +122,7 @@ Which will render as:
|
|||
{% 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)
|
||||
```htmldjango
|
||||
{% component "calendar" date="2024-12-13" %}
|
||||
|
@ -177,7 +177,7 @@ def to_workweek_date(d: date):
|
|||
|
||||
@register("calendar")
|
||||
class Calendar(Component):
|
||||
template_name = "calendar.html"
|
||||
template_file = "calendar.html"
|
||||
...
|
||||
def get_context_data(self, date: date, extra_class: str | None = None):
|
||||
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.
|
||||
|
||||
### 5. Adding data to slots
|
||||
|
|
|
@ -31,7 +31,7 @@ from django_components import Component, register # <--- new
|
|||
|
||||
@register("calendar") # <--- new
|
||||
class Calendar(Component):
|
||||
template_name = "calendar.html"
|
||||
template_file = "calendar.html"
|
||||
js_file = "calendar.js"
|
||||
css_file = "calendar.css"
|
||||
|
||||
|
|
|
@ -23,7 +23,7 @@ from django_components import Component, register
|
|||
|
||||
@register("calendar")
|
||||
class Calendar(Component):
|
||||
template_name = "calendar.html"
|
||||
template_file = "calendar.html"
|
||||
...
|
||||
def get_context_data(self):
|
||||
return {
|
||||
|
@ -71,7 +71,7 @@ from django_components import Component, register
|
|||
|
||||
@register("calendar")
|
||||
class Calendar(Component):
|
||||
template_name = "calendar.html"
|
||||
template_file = "calendar.html"
|
||||
...
|
||||
def get_context_data(self, date: date, extra_class: str | None = None):
|
||||
return {
|
||||
|
@ -135,7 +135,7 @@ We need to group the list items by size into following buckets by population:
|
|||
|
||||
- 0-10,000,000
|
||||
- 10,000,001-20,000,000
|
||||
- 20,000,001-30,000,000
|
||||
- 20,000,001-30,000,000
|
||||
- +30,000,001
|
||||
|
||||
So we want to end up with following data:
|
||||
|
@ -179,7 +179,7 @@ def group_by_pop(data):
|
|||
|
||||
@register("population_table")
|
||||
class PopulationTable(Component):
|
||||
template_name = "population_table.html"
|
||||
template_file = "population_table.html"
|
||||
|
||||
def get_context_data(self, data):
|
||||
return {
|
||||
|
@ -200,7 +200,7 @@ def to_workweek_date(d: date):
|
|||
|
||||
@register("calendar")
|
||||
class Calendar(Component):
|
||||
template_name = "calendar.html"
|
||||
template_file = "calendar.html"
|
||||
...
|
||||
def get_context_data(self, date: date, extra_class: str | None = None):
|
||||
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
|
||||
|
||||
class Calendar(Component):
|
||||
template_name = "calendar.html"
|
||||
template_file = "calendar.html"
|
||||
```
|
||||
|
||||
Or a combination of Django template, Python, CSS, and Javascript:
|
||||
|
@ -43,7 +43,7 @@ document.querySelector(".calendar").onclick = function () {
|
|||
from django_components import Component
|
||||
|
||||
class Calendar(Component):
|
||||
template_name = "calendar.html"
|
||||
template_file = "calendar.html"
|
||||
js_file = "calendar.js"
|
||||
css_file = "calendar.css"
|
||||
```
|
||||
|
@ -80,7 +80,6 @@ class Calendar(Component):
|
|||
[syntax highlighting](../../guides/setup/syntax_highlight.md) 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:
|
||||
|
||||
### 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)
|
||||
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.
|
||||
|
||||
```python title="[project root]/components/calendar/calendar.py"
|
||||
from django_components import Component
|
||||
|
||||
class Calendar(Component):
|
||||
template_name = "calendar.html"
|
||||
template_file = "calendar.html"
|
||||
```
|
||||
|
||||
!!! note
|
||||
|
@ -156,7 +155,7 @@ will become available within the template as variables, e.g. as `{{ date }}`.
|
|||
from django_components import Component
|
||||
|
||||
class Calendar(Component):
|
||||
template_name = "calendar.html"
|
||||
template_file = "calendar.html"
|
||||
|
||||
def get_context_data(self):
|
||||
return {
|
||||
|
|
|
@ -65,7 +65,7 @@
|
|||
```py
|
||||
@register("my_comp")
|
||||
class MyComp(Component):
|
||||
template_name = "abc.html"
|
||||
template_file = "abc.html"
|
||||
```
|
||||
|
||||
Then:
|
||||
|
@ -112,7 +112,7 @@
|
|||
````py
|
||||
@register("my_comp")
|
||||
class MyComp(Component):
|
||||
template_name = """
|
||||
template_file = """
|
||||
{% extends "abc.html" %}
|
||||
|
||||
{% block inner %}
|
||||
|
@ -129,7 +129,7 @@
|
|||
```py
|
||||
@register("my_comp")
|
||||
class MyComp(Component):
|
||||
template_name = """
|
||||
template_file = """
|
||||
{% extends "abc.html" %}
|
||||
|
||||
{% load component_tags %}
|
||||
|
|
|
@ -2,11 +2,11 @@
|
|||
title: Welcome to Django Components
|
||||
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;">
|
||||
|
||||
[](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.
|
||||
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
|
||||
|
||||
class Calendar(Component):
|
||||
template_name = "calendar.html"
|
||||
template_file = "calendar.html"
|
||||
```
|
||||
|
||||
Or a combination of Django template, Python, CSS, and Javascript:
|
||||
|
@ -50,7 +50,7 @@ document.querySelector(".calendar").onclick = function () {
|
|||
from django_components import Component
|
||||
|
||||
class Calendar(Component):
|
||||
template_name = "calendar.html"
|
||||
template_file = "calendar.html"
|
||||
js_file = "calendar.js"
|
||||
css_file = "calendar.css"
|
||||
```
|
||||
|
@ -100,13 +100,14 @@ Django-components can be particularly useful for larger Django projects that req
|
|||
|
||||
## 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:
|
||||
|
||||
```python
|
||||
@register("calendar")
|
||||
class Calendar(Component):
|
||||
template_name = "template.html"
|
||||
template_file = "template.html"
|
||||
|
||||
def get_context_data(self, date):
|
||||
return {"date": date}
|
||||
|
|
|
@ -6,8 +6,8 @@ class Calendar(Component):
|
|||
# Templates inside `[your apps]/components` dir and `[project root]/components` dir
|
||||
# will be automatically found.
|
||||
#
|
||||
# `template_name` can be relative to dir where `calendar.py` is, or relative to COMPONENTS.dirs
|
||||
template_name = "calendar/calendar.html"
|
||||
# `template_file` can be relative to dir where `calendar.py` is, or relative to COMPONENTS.dirs
|
||||
template_file = "calendar/calendar.html"
|
||||
|
||||
css_file = "calendar/calendar.css"
|
||||
js_file = "calendar/calendar.js"
|
||||
|
@ -31,8 +31,8 @@ class CalendarRelative(Component):
|
|||
# Templates inside `[your apps]/components` dir and `[project root]/components` dir
|
||||
# will be automatically found.
|
||||
#
|
||||
# `template_name` can be relative to dir where `calendar.py` is, or relative to COMPONENTS.dirs
|
||||
template_name = "calendar.html"
|
||||
# `template_file` can be relative to dir where `calendar.py` is, or relative to COMPONENTS.dirs
|
||||
template_file = "calendar.html"
|
||||
|
||||
css_file = "calendar.css"
|
||||
js_file = "calendar.js"
|
||||
|
|
|
@ -6,8 +6,8 @@ class CalendarNested(Component):
|
|||
# Templates inside `[your apps]/components` dir and `[project root]/components` dir
|
||||
# will be automatically found.
|
||||
#
|
||||
# `template_name` can be relative to dir where `calendar.py` is, or relative to COMPONENTS.dirs
|
||||
template_name = "calendar.html"
|
||||
# `template_file` can be relative to dir where `calendar.py` is, or relative to COMPONENTS.dirs
|
||||
template_file = "calendar.html"
|
||||
|
||||
css_file = "calendar.css"
|
||||
js_file = "calendar.js"
|
||||
|
|
|
@ -5,4 +5,4 @@ from django_components import Component, register
|
|||
class Todo(Component):
|
||||
# Templates inside `[your apps]/components` dir and `[project root]/components` dir
|
||||
# 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):
|
||||
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
|
||||
# in `View.http_method_names`.
|
||||
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
|
||||
def create_handler(method: str) -> Callable:
|
||||
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:
|
||||
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):
|
||||
|
@ -205,14 +223,14 @@ class Component(
|
|||
# PUBLIC API (Configurable by users)
|
||||
# #####################################
|
||||
|
||||
template_name: Optional[str] = None
|
||||
template_file: Optional[str] = None
|
||||
"""
|
||||
Filepath to the Django template associated with this component.
|
||||
|
||||
The filepath must be relative to either the file where the component class was defined,
|
||||
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),
|
||||
[`template`](../api#django_components.Component.template)
|
||||
or [`get_template`](../api#django_components.Component.get_template) must be defined.
|
||||
|
@ -221,13 +239,27 @@ class Component(
|
|||
|
||||
```py
|
||||
class MyComponent(Component):
|
||||
template_name = "path/to/template.html"
|
||||
template_file = "path/to/template.html"
|
||||
|
||||
def get_context_data(self):
|
||||
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]:
|
||||
"""
|
||||
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,
|
||||
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),
|
||||
[`template`](../api#django_components.Component.template)
|
||||
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.
|
||||
|
||||
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),
|
||||
[`template`](../api#django_components.Component.template)
|
||||
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.
|
||||
|
||||
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),
|
||||
[`template`](../api#django_components.Component.template)
|
||||
or [`get_template`](../api#django_components.Component.get_template) must be defined.
|
||||
|
@ -596,21 +628,21 @@ class Component(
|
|||
|
||||
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
|
||||
# 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.
|
||||
def _get_template(self, context: Context) -> Template:
|
||||
# Resolve template name
|
||||
template_name = self.template_name
|
||||
if self.template_name is not None:
|
||||
template_file = self.template_file
|
||||
if self.template_file is not None:
|
||||
if self.get_template_name(context) is not None:
|
||||
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."
|
||||
)
|
||||
else:
|
||||
template_name = self.get_template_name(context)
|
||||
template_file = self.get_template_name(context)
|
||||
|
||||
# Resolve template str
|
||||
template_input = self.template
|
||||
|
@ -625,14 +657,14 @@ class Component(
|
|||
template_getter = getattr(self, "get_template_string", self.get_template)
|
||||
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(
|
||||
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."
|
||||
)
|
||||
|
||||
if template_name is not None:
|
||||
return get_template(template_name).template
|
||||
if template_file is not None:
|
||||
return get_template(template_file).template
|
||||
|
||||
elif template_input is not None:
|
||||
# We got template string, so we convert it to Template
|
||||
|
@ -644,7 +676,7 @@ class Component(
|
|||
return template
|
||||
|
||||
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:
|
||||
|
|
|
@ -18,7 +18,7 @@ if TYPE_CHECKING:
|
|||
|
||||
|
||||
# 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[
|
||||
|
@ -176,7 +176,7 @@ class ComponentMedia:
|
|||
media: Optional[MediaCls] = None
|
||||
media_class: Type[MediaCls] = MediaCls
|
||||
template: Optional[str] = None
|
||||
template_name: Optional[str] = None
|
||||
template_file: Optional[str] = None
|
||||
js: Optional[str] = None
|
||||
js_file: 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.
|
||||
#
|
||||
# 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
|
||||
# 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:
|
||||
# - 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."
|
||||
)
|
||||
|
||||
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.
|
||||
|
@ -254,7 +261,7 @@ def _setup_lazy_media_resolve(comp_cls: Type["Component"], attrs: Dict[str, Any]
|
|||
media=attrs.get("media", None),
|
||||
media_class=attrs.get("media_class", 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_file=attrs.get("js_file", None),
|
||||
css=attrs.get("css", None),
|
||||
|
@ -292,8 +299,8 @@ def _setup_lazy_media_resolve(comp_cls: Type["Component"], attrs: Dict[str, Any]
|
|||
continue
|
||||
else:
|
||||
return value
|
||||
if attr in ("template", "template_name"):
|
||||
if check_pair_empty("template", "template_name"):
|
||||
if attr in ("template", "template_file"):
|
||||
if check_pair_empty("template", "template_file"):
|
||||
continue
|
||||
else:
|
||||
return value
|
||||
|
@ -544,7 +551,7 @@ def _resolve_component_relative_files(
|
|||
# HTML/JS/CSS files, just skip.
|
||||
will_resolve_files = False
|
||||
if (
|
||||
getattr(comp_media, "template_name", None)
|
||||
getattr(comp_media, "template_file", None)
|
||||
or getattr(comp_media, "js_file", None)
|
||||
or getattr(comp_media, "css_file", None)
|
||||
):
|
||||
|
@ -609,8 +616,8 @@ def _resolve_component_relative_files(
|
|||
return filepath
|
||||
|
||||
# Check if template name is a local file or not
|
||||
if getattr(comp_media, "template_name", None):
|
||||
comp_media.template_name = resolve_media_file(comp_media.template_name)
|
||||
if getattr(comp_media, "template_file", None):
|
||||
comp_media.template_file = resolve_media_file(comp_media.template_file)
|
||||
if getattr(comp_media, "js_file", None):
|
||||
comp_media.js_file = resolve_media_file(comp_media.js_file)
|
||||
if getattr(comp_media, "css_file", None):
|
||||
|
|
|
@ -197,7 +197,7 @@ class Command(BaseCommand):
|
|||
|
||||
@register("{name}")
|
||||
class {name.capitalize()}(Component):
|
||||
template_name = "{name}/{template_filename}"
|
||||
template_file = "{name}/{template_filename}"
|
||||
|
||||
def get_context_data(self, value):
|
||||
return {{
|
||||
|
|
|
@ -7,7 +7,7 @@ from django_components import Component, register
|
|||
|
||||
@register("multi_file_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:
|
||||
variable = request.POST.get("variable")
|
||||
|
|
|
@ -7,7 +7,7 @@ from django_components import Component, register
|
|||
|
||||
@register("relative_file_component")
|
||||
class RelativeFileComponent(Component):
|
||||
template_name = "relative_file.html"
|
||||
template_file = "relative_file.html"
|
||||
|
||||
class Media:
|
||||
js = "relative_file.js"
|
||||
|
|
|
@ -26,7 +26,7 @@ class PathObj:
|
|||
|
||||
@register("relative_file_pathobj_component")
|
||||
class RelativeFileWithPathObjComponent(Component):
|
||||
template_name = "relative_file_pathobj.html"
|
||||
template_file = "relative_file_pathobj.html"
|
||||
|
||||
class Media:
|
||||
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`
|
||||
@register("staticfiles_component")
|
||||
class RelativeFileWithPathObjComponent(Component):
|
||||
template_name = "staticfiles.html"
|
||||
template_file = "staticfiles.html"
|
||||
|
||||
class Media:
|
||||
js = "staticfiles.js"
|
||||
|
|
|
@ -6,7 +6,7 @@ from django_components import Component, register
|
|||
# Used for testing the template_loader
|
||||
@register("app_lvl_comp")
|
||||
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"
|
||||
css_file = "app_lvl_comp.css"
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@ from django_components import Component, register
|
|||
# Used for testing the template_loader
|
||||
@register("custom_app_lvl_comp")
|
||||
class AppLvlCompComponent(Component):
|
||||
template_name = "app_lvl_comp.html"
|
||||
template_file = "app_lvl_comp.html"
|
||||
|
||||
class Media:
|
||||
js = "app_lvl_comp.js"
|
||||
|
|
|
@ -206,9 +206,9 @@ class ComponentTest(BaseTestCase):
|
|||
)
|
||||
|
||||
@parametrize_context_behavior(["django", "isolated"])
|
||||
def test_template_name_static(self):
|
||||
def test_template_file_static(self):
|
||||
class SimpleComponent(Component):
|
||||
template_name = "simple_template.html"
|
||||
template_file = "simple_template.html"
|
||||
|
||||
def get_context_data(self, variable=None):
|
||||
return {
|
||||
|
@ -228,7 +228,58 @@ class ComponentTest(BaseTestCase):
|
|||
)
|
||||
|
||||
@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):
|
||||
def get_context_data(self, name, css_class="", title="", **attrs):
|
||||
return {
|
||||
|
|
|
@ -31,7 +31,7 @@ class MainMediaTest(BaseTestCase):
|
|||
|
||||
def test_html_filepath(self):
|
||||
class Test(Component):
|
||||
template_name = "simple_template.html"
|
||||
template_file = "simple_template.html"
|
||||
|
||||
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
|
||||
|
||||
class TestComponent(AppLvlCompComponent):
|
||||
template_name = None
|
||||
template_file = None
|
||||
template = """
|
||||
{% load component_tags %}
|
||||
{% component_js_dependencies %}
|
||||
|
@ -167,7 +167,7 @@ class MainMediaTest(BaseTestCase):
|
|||
from tests.test_app.components.app_lvl_comp.app_lvl_comp import AppLvlCompComponent
|
||||
|
||||
class TestComponent(AppLvlCompComponent):
|
||||
template_name = None
|
||||
template_file = None
|
||||
template = """
|
||||
{% load component_tags %}
|
||||
{% component_js_dependencies %}
|
||||
|
|
|
@ -13,7 +13,7 @@ setup_test_config({"autodiscover": False})
|
|||
|
||||
|
||||
class SlottedComponent(Component):
|
||||
template_name = "slotted_template.html"
|
||||
template_file = "slotted_template.html"
|
||||
|
||||
|
||||
#######################
|
||||
|
@ -42,7 +42,7 @@ class TemplateInstrumentationTest(BaseTestCase):
|
|||
|
||||
@register("inner_component")
|
||||
class SimpleComponent(Component):
|
||||
template_name = "simple_template.html"
|
||||
template_file = "simple_template.html"
|
||||
|
||||
def get_context_data(self, variable, variable2="default"):
|
||||
return {
|
||||
|
|
|
@ -9,7 +9,7 @@ setup_test_config({"autodiscover": False})
|
|||
|
||||
|
||||
class SlottedComponent(Component):
|
||||
template_name = "slotted_template.html"
|
||||
template_file = "slotted_template.html"
|
||||
|
||||
|
||||
class SlottedComponentWithContext(Component):
|
||||
|
|
|
@ -11,11 +11,11 @@ setup_test_config({"autodiscover": False})
|
|||
|
||||
|
||||
class SlottedComponent(Component):
|
||||
template_name = "slotted_template.html"
|
||||
template_file = "slotted_template.html"
|
||||
|
||||
|
||||
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")
|
||||
class _ExtendedComponent(Component):
|
||||
template_name = "included.html"
|
||||
template_file = "included.html"
|
||||
|
||||
template: types.django_html = """
|
||||
{% extends 'block.html' %}
|
||||
|
@ -587,7 +587,7 @@ class ExtendsCompatTests(BaseTestCase):
|
|||
|
||||
@register("block_in_component_parent")
|
||||
class BlockInCompParent(Component):
|
||||
template_name = "block_in_component_parent.html"
|
||||
template_file = "block_in_component_parent.html"
|
||||
|
||||
template: types.django_html = """
|
||||
{% load component_tags %}
|
||||
|
@ -620,7 +620,7 @@ class ExtendsCompatTests(BaseTestCase):
|
|||
|
||||
@register("block_inside_slot_v1")
|
||||
class BlockInSlotInComponent(Component):
|
||||
template_name = "block_in_slot_in_component.html"
|
||||
template_file = "block_in_slot_in_component.html"
|
||||
|
||||
template: types.django_html = """
|
||||
{% load component_tags %}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue