mirror of
https://github.com/django-components/django-components.git
synced 2025-08-11 09:48:00 +00:00
chore: Push dev to master to release v0.110 (#767)
* feat: skeleton of dependency manager backend (#688) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> * refactor: selectolax update and tests cleanup (#702) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> * refactor: move release notes to own file (#704) * chore: merge changes from master (#705) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Yassin Rakha <yaso2go@gmail.com> Co-authored-by: Emil Stenström <emil@emilstenstrom.se> fix for nested slots (#698) (#699) * refactor: remove joint {% component_dependencies %} tag (#706) Co-authored-by: Emil Stenström <emil@emilstenstrom.se> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> * refactor: split up utils file and move utils to util dir (#707) * docs: Move docs inside src/ to allow imports in python scripts (#708) * refactor: Docs prep 1 (#715) * refactor: Document template tags (#716) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> * refactor: pass slot fills in template via slots param (#719) * chore: Merge master to dev (#729) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Yassin Rakha <yaso2go@gmail.com> Co-authored-by: Emil Stenström <emil@emilstenstrom.se> Co-authored-by: Tom Larsen <larsent@gmail.com> fix for nested slots (#698) (#699) * fix: Do not raise error if multiple slots with same name are flagged as default (#727) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> * refactor: tag formatter - allow fwd slash in end tag (#730) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> * refactor: Use lowercase names for registry settings (#731) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> * docs: add docstrings (#732) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> * feat: define settings as a data class for type hints, intellisense, and docs (#733) * refactor: fix reload-on-change logic, expose autodiscover's dirs-getting logic, rename settings (#734) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> * docs: document settings (#743) * docs: document settings * refactor: fix linter errors * feat: passthrough slots and more (#758) * feat: passthrough slots and more * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * refactor: remove ComponentSlotContext.slots * refactor: update comment * docs: update changelog * refactor: update docstrings * refactor: document and test-cover more changes * refactor: revert fill without name * docs: update README --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> * fix: apostrophes in tags (#765) * refactor: fix merge error - duplicate code --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Emil Stenström <emil@emilstenstrom.se>
This commit is contained in:
parent
9f891453d5
commit
5fd45ab424
97 changed files with 8727 additions and 3011 deletions
|
@ -1,24 +1,60 @@
|
|||
import re
|
||||
from dataclasses import dataclass
|
||||
from enum import Enum
|
||||
from os import PathLike
|
||||
from pathlib import Path
|
||||
from typing import TYPE_CHECKING, Dict, List, Tuple, Union
|
||||
from typing import (
|
||||
TYPE_CHECKING,
|
||||
Callable,
|
||||
Generic,
|
||||
List,
|
||||
Literal,
|
||||
NamedTuple,
|
||||
Optional,
|
||||
Sequence,
|
||||
Tuple,
|
||||
TypeVar,
|
||||
Union,
|
||||
cast,
|
||||
)
|
||||
|
||||
from django.conf import settings
|
||||
|
||||
from django_components.util.misc import default
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from django_components.tag_formatter import TagFormatterABC
|
||||
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
|
||||
ContextBehaviorType = Literal["django", "isolated"]
|
||||
|
||||
|
||||
class ContextBehavior(str, Enum):
|
||||
"""
|
||||
Configure how (and whether) the context is passed to the component fills
|
||||
and what variables are available inside the [`{% fill %}`](../template_tags#fill) tags.
|
||||
|
||||
Also see [Component context and scope](../../concepts/fundamentals/component_context_scope#context-behavior).
|
||||
|
||||
**Options:**
|
||||
|
||||
- `django`: With this setting, component fills behave as usual Django tags.
|
||||
- `isolated`: This setting makes the component fills behave similar to Vue or React.
|
||||
"""
|
||||
|
||||
DJANGO = "django"
|
||||
"""
|
||||
With this setting, component fills behave as usual Django tags.
|
||||
That is, they enrich the context, and pass it along.
|
||||
|
||||
1. Component fills use the context of the component they are within.
|
||||
2. Variables from `get_context_data` are available to the component fill.
|
||||
2. Variables from [`Component.get_context_data()`](../api#django_components.Component.get_context_data)
|
||||
are available to the component fill.
|
||||
|
||||
Example:
|
||||
**Example:**
|
||||
|
||||
Given this template
|
||||
```django
|
||||
|
@ -30,13 +66,13 @@ class ContextBehavior(str, Enum):
|
|||
{% endwith %}
|
||||
```
|
||||
|
||||
and this context returned from the `get_context_data()` method
|
||||
```py
|
||||
and this context returned from the `Component.get_context_data()` method
|
||||
```python
|
||||
{ "my_var": 123 }
|
||||
```
|
||||
|
||||
Then if component "my_comp" defines context
|
||||
```py
|
||||
```python
|
||||
{ "my_var": 456 }
|
||||
```
|
||||
|
||||
|
@ -56,9 +92,10 @@ class ContextBehavior(str, Enum):
|
|||
ISOLATED = "isolated"
|
||||
"""
|
||||
This setting makes the component fills behave similar to Vue or React, where
|
||||
the fills use EXCLUSIVELY the context variables defined in `get_context_data`.
|
||||
the fills use EXCLUSIVELY the context variables defined in
|
||||
[`Component.get_context_data()`](../api#django_components.Component.get_context_data).
|
||||
|
||||
Example:
|
||||
**Example:**
|
||||
|
||||
Given this template
|
||||
```django
|
||||
|
@ -71,12 +108,12 @@ class ContextBehavior(str, Enum):
|
|||
```
|
||||
|
||||
and this context returned from the `get_context_data()` method
|
||||
```py
|
||||
```python
|
||||
{ "my_var": 123 }
|
||||
```
|
||||
|
||||
Then if component "my_comp" defines context
|
||||
```py
|
||||
```python
|
||||
{ "my_var": 456 }
|
||||
```
|
||||
|
||||
|
@ -91,95 +128,565 @@ class ContextBehavior(str, Enum):
|
|||
"""
|
||||
|
||||
|
||||
class AppSettings:
|
||||
# This is the source of truth for the settings that are available. If the documentation
|
||||
# or the defaults do NOT match this, they should be updated.
|
||||
class ComponentsSettings(NamedTuple):
|
||||
"""
|
||||
Settings available for django_components.
|
||||
|
||||
**Example:**
|
||||
|
||||
```python
|
||||
COMPONENTS = ComponentsSettings(
|
||||
autodiscover=False,
|
||||
dirs = [BASE_DIR / "components"],
|
||||
)
|
||||
```
|
||||
"""
|
||||
|
||||
autodiscover: Optional[bool] = None
|
||||
"""
|
||||
Toggle whether to run [autodiscovery](../../concepts/fundamentals/autodiscovery) at the Django server startup.
|
||||
|
||||
Defaults to `True`
|
||||
|
||||
```python
|
||||
COMPONENTS = ComponentsSettings(
|
||||
autodiscover=False,
|
||||
)
|
||||
```
|
||||
"""
|
||||
|
||||
dirs: Optional[Sequence[Union[str, PathLike, Tuple[str, str], Tuple[str, PathLike]]]] = None
|
||||
"""
|
||||
Specify the directories that contain your components.
|
||||
|
||||
Defaults to `[Path(settings.BASE_DIR) / "components"]`. That is, the root `components/` app.
|
||||
|
||||
Directories must be full paths, same as with
|
||||
[STATICFILES_DIRS](https://docs.djangoproject.com/en/5.1/ref/settings/#std-setting-STATICFILES_DIRS).
|
||||
|
||||
These locations are searched during [autodiscovery](../../concepts/fundamentals/autodiscovery),
|
||||
or when you [define HTML, JS, or CSS as separate files](../../concepts/fundamentals/defining_js_css_html_files).
|
||||
|
||||
```python
|
||||
COMPONENTS = ComponentsSettings(
|
||||
dirs=[BASE_DIR / "components"],
|
||||
)
|
||||
```
|
||||
|
||||
Set to empty list to disable global components directories:
|
||||
|
||||
```python
|
||||
COMPONENTS = ComponentsSettings(
|
||||
dirs=[],
|
||||
)
|
||||
```
|
||||
"""
|
||||
|
||||
app_dirs: Optional[Sequence[str]] = None
|
||||
"""
|
||||
Specify the app-level directories that contain your components.
|
||||
|
||||
Defaults to `["components"]`. That is, for each Django app, we search `<app>/components/` for components.
|
||||
|
||||
The paths must be relative to app, e.g.:
|
||||
|
||||
```python
|
||||
COMPONENTS = ComponentsSettings(
|
||||
app_dirs=["my_comps"],
|
||||
)
|
||||
```
|
||||
|
||||
To search for `<app>/my_comps/`.
|
||||
|
||||
These locations are searched during [autodiscovery](../../concepts/fundamentals/autodiscovery),
|
||||
or when you [define HTML, JS, or CSS as separate files](../../concepts/fundamentals/defining_js_css_html_files).
|
||||
|
||||
Set to empty list to disable app-level components:
|
||||
|
||||
```python
|
||||
COMPONENTS = ComponentsSettings(
|
||||
app_dirs=[],
|
||||
)
|
||||
```
|
||||
"""
|
||||
|
||||
context_behavior: Optional[ContextBehaviorType] = None
|
||||
"""
|
||||
Configure whether, inside a component template, you can use variables from the outside
|
||||
([`"django"`](../api#django_components.ContextBehavior.DJANGO))
|
||||
or not ([`"isolated"`](../api#django_components.ContextBehavior.ISOLATED)).
|
||||
This also affects what variables are available inside the [`{% fill %}`](../template_tags#fill)
|
||||
tags.
|
||||
|
||||
Also see [Component context and scope](../../concepts/fundamentals/component_context_scope#context-behavior).
|
||||
|
||||
Defaults to `"django"`.
|
||||
|
||||
```python
|
||||
COMPONENTS = ComponentsSettings(
|
||||
context_behavior="isolated",
|
||||
)
|
||||
```
|
||||
|
||||
> NOTE: `context_behavior` and `slot_context_behavior` options were merged in v0.70.
|
||||
>
|
||||
> If you are migrating from BEFORE v0.67, set `context_behavior` to `"django"`.
|
||||
> From v0.67 to v0.78 (incl) the default value was `"isolated"`.
|
||||
>
|
||||
> For v0.79 and later, the default is again `"django"`. See the rationale for change
|
||||
> [here](https://github.com/EmilStenstrom/django-components/issues/498).
|
||||
"""
|
||||
|
||||
dynamic_component_name: Optional[str] = None
|
||||
"""
|
||||
By default, the [dynamic component](../components#django_components.components.dynamic.DynamicComponent)
|
||||
is registered under the name `"dynamic"`.
|
||||
|
||||
In case of a conflict, you can use this setting to change the component name used for
|
||||
the dynamic components.
|
||||
|
||||
```python
|
||||
# settings.py
|
||||
COMPONENTS = ComponentsSettings(
|
||||
dynamic_component_name="my_dynamic",
|
||||
)
|
||||
```
|
||||
|
||||
After which you will be able to use the dynamic component with the new name:
|
||||
|
||||
```django
|
||||
{% component "my_dynamic" is=table_comp data=table_data headers=table_headers %}
|
||||
{% fill "pagination" %}
|
||||
{% component "pagination" / %}
|
||||
{% endfill %}
|
||||
{% endcomponent %}
|
||||
```
|
||||
"""
|
||||
|
||||
libraries: Optional[List[str]] = None
|
||||
"""
|
||||
Configure extra python modules that should be loaded.
|
||||
|
||||
This may be useful if you are not using the [autodiscovery feature](../../concepts/fundamentals/autodiscovery),
|
||||
or you need to load components from non-standard locations. Thus you can have
|
||||
a structure of components that is independent from your apps.
|
||||
|
||||
Expects a list of python module paths. Defaults to empty list.
|
||||
|
||||
**Example:**
|
||||
|
||||
```python
|
||||
COMPONENTS = ComponentsSettings(
|
||||
libraries=[
|
||||
"mysite.components.forms",
|
||||
"mysite.components.buttons",
|
||||
"mysite.components.cards",
|
||||
],
|
||||
)
|
||||
```
|
||||
|
||||
This would be the equivalent of importing these modules from within Django's
|
||||
[`AppConfig.ready()`](https://docs.djangoproject.com/en/5.1/ref/applications/#django.apps.AppConfig.ready):
|
||||
|
||||
```python
|
||||
class MyAppConfig(AppConfig):
|
||||
def ready(self):
|
||||
import "mysite.components.forms"
|
||||
import "mysite.components.buttons"
|
||||
import "mysite.components.cards"
|
||||
```
|
||||
|
||||
# Manually loading libraries
|
||||
|
||||
In the rare case that you need to manually trigger the import of libraries, you can use
|
||||
the [`import_libraries()`](../api/#django_components.import_libraries) function:
|
||||
|
||||
```python
|
||||
from django_components import import_libraries
|
||||
|
||||
import_libraries()
|
||||
```
|
||||
"""
|
||||
|
||||
multiline_tags: Optional[bool] = None
|
||||
"""
|
||||
Enable / disable
|
||||
[multiline support for template tags](../../concepts/fundamentals/template_tag_syntax#multiline-tags).
|
||||
If `True`, template tags like `{% component %}` or `{{ my_var }}` can span multiple lines.
|
||||
|
||||
Defaults to `True`.
|
||||
|
||||
Disable this setting if you are making custom modifications to Django's
|
||||
regular expression for parsing templates at `django.template.base.tag_re`.
|
||||
|
||||
```python
|
||||
COMPONENTS = ComponentsSettings(
|
||||
multiline_tags=False,
|
||||
)
|
||||
```
|
||||
"""
|
||||
|
||||
# TODO_REMOVE_IN_V1
|
||||
reload_on_template_change: Optional[bool] = None
|
||||
"""Deprecated. Use
|
||||
[`COMPONENTS.reload_on_file_change`](../settings/#django_components.app_settings.ComponentsSettings.reload_on_file_change)
|
||||
instead.""" # noqa: E501
|
||||
|
||||
reload_on_file_change: Optional[bool] = None
|
||||
"""
|
||||
This is relevant if you are using the project structure where
|
||||
HTML, JS, CSS and Python are in separate files and nested in a directory.
|
||||
|
||||
In this case you may notice that when you are running a development server,
|
||||
the server sometimes does not reload when you change component files.
|
||||
|
||||
Django's native [live reload](https://stackoverflow.com/a/66023029/9788634) logic
|
||||
handles only Python files and HTML template files. It does NOT reload when other
|
||||
file types change or when template files are nested more than one level deep.
|
||||
|
||||
The setting `reload_on_file_change` fixes this, reloading the dev server even when your component's
|
||||
HTML, JS, or CSS changes.
|
||||
|
||||
If `True`, django_components configures Django to reload when files inside
|
||||
[`COMPONENTS.dirs`](../settings/#django_components.app_settings.ComponentsSettings.dirs)
|
||||
or
|
||||
[`COMPONENTS.app_dirs`](../settings/#django_components.app_settings.ComponentsSettings.app_dirs)
|
||||
change.
|
||||
|
||||
See [Reload dev server on component file changes](../../guides/setup/dev_server_setup/#reload-dev-server-on-component-file-changes).
|
||||
|
||||
Defaults to `False`.
|
||||
|
||||
!!! warning
|
||||
|
||||
This setting should be enabled only for the dev environment!
|
||||
""" # noqa: E501
|
||||
|
||||
static_files_allowed: Optional[List[Union[str, re.Pattern]]] = None
|
||||
"""
|
||||
A list of file extensions (including the leading dot) that define which files within
|
||||
[`COMPONENTS.dirs`](../settings/#django_components.app_settings.ComponentsSettings.dirs)
|
||||
or
|
||||
[`COMPONENTS.app_dirs`](../settings/#django_components.app_settings.ComponentsSettings.app_dirs)
|
||||
are treated as [static files](https://docs.djangoproject.com/en/5.1/howto/static-files/).
|
||||
|
||||
If a file is matched against any of the patterns, it's considered a static file. Such files are collected
|
||||
when running [`collectstatic`](https://docs.djangoproject.com/en/5.1/ref/contrib/staticfiles/#collectstatic),
|
||||
and can be accessed under the
|
||||
[static file endpoint](https://docs.djangoproject.com/en/5.1/ref/settings/#static-url).
|
||||
|
||||
You can also pass in compiled regexes ([`re.Pattern`](https://docs.python.org/3/library/re.html#re.Pattern))
|
||||
for more advanced patterns.
|
||||
|
||||
By default, JS, CSS, and common image and font file formats are considered static files:
|
||||
|
||||
```python
|
||||
COMPONENTS = ComponentsSettings(
|
||||
static_files_allowed=[
|
||||
".css",
|
||||
".js", ".jsx", ".ts", ".tsx",
|
||||
# Images
|
||||
".apng", ".png", ".avif", ".gif", ".jpg",
|
||||
".jpeg", ".jfif", ".pjpeg", ".pjp", ".svg",
|
||||
".webp", ".bmp", ".ico", ".cur", ".tif", ".tiff",
|
||||
# Fonts
|
||||
".eot", ".ttf", ".woff", ".otf", ".svg",
|
||||
],
|
||||
)
|
||||
```
|
||||
|
||||
!!! warning
|
||||
|
||||
Exposing your Python files can be a security vulnerability.
|
||||
See [Security notes](../../overview/security_notes).
|
||||
"""
|
||||
|
||||
# TODO_REMOVE_IN_V1
|
||||
forbidden_static_files: Optional[List[Union[str, re.Pattern]]] = None
|
||||
"""Deprecated. Use
|
||||
[`COMPONENTS.static_files_forbidden`](../settings/#django_components.app_settings.ComponentsSettings.static_files_forbidden)
|
||||
instead.""" # noqa: E501
|
||||
|
||||
static_files_forbidden: Optional[List[Union[str, re.Pattern]]] = None
|
||||
"""
|
||||
A list of file extensions (including the leading dot) that define which files within
|
||||
[`COMPONENTS.dirs`](../settings/#django_components.app_settings.ComponentsSettings.dirs)
|
||||
or
|
||||
[`COMPONENTS.app_dirs`](../settings/#django_components.app_settings.ComponentsSettings.app_dirs)
|
||||
will NEVER be treated as [static files](https://docs.djangoproject.com/en/5.1/howto/static-files/).
|
||||
|
||||
If a file is matched against any of the patterns, it will never be considered a static file,
|
||||
even if the file matches a pattern in
|
||||
[`static_files_allowed`](../settings/#django_components.app_settings.ComponentsSettings.static_files_allowed).
|
||||
|
||||
Use this setting together with
|
||||
[`static_files_allowed`](../settings/#django_components.app_settings.ComponentsSettings.static_files_allowed)
|
||||
for a fine control over what file types will be exposed.
|
||||
|
||||
You can also pass in compiled regexes ([`re.Pattern`](https://docs.python.org/3/library/re.html#re.Pattern))
|
||||
for more advanced patterns.
|
||||
|
||||
By default, any HTML and Python are considered NOT static files:
|
||||
|
||||
```python
|
||||
COMPONENTS = ComponentsSettings(
|
||||
static_files_forbidden=[
|
||||
".html", ".django", ".dj", ".tpl",
|
||||
# Python files
|
||||
".py", ".pyc",
|
||||
],
|
||||
)
|
||||
```
|
||||
|
||||
!!! warning
|
||||
|
||||
Exposing your Python files can be a security vulnerability.
|
||||
See [Security notes](../../overview/security_notes).
|
||||
"""
|
||||
|
||||
tag_formatter: Optional[Union["TagFormatterABC", str]] = None
|
||||
"""
|
||||
Configure what syntax is used inside Django templates to render components.
|
||||
See the [available tag formatters](../tag_formatters).
|
||||
|
||||
Defaults to `"django_components.component_formatter"`.
|
||||
|
||||
Learn more about [Customizing component tags with TagFormatter](../../concepts/advanced/tag_formatter).
|
||||
|
||||
Can be set either as direct reference:
|
||||
|
||||
```python
|
||||
from django_components import component_formatter
|
||||
|
||||
COMPONENTS = ComponentsSettings(
|
||||
"tag_formatter": component_formatter
|
||||
)
|
||||
```
|
||||
|
||||
Or as an import string;
|
||||
|
||||
```python
|
||||
COMPONENTS = ComponentsSettings(
|
||||
"tag_formatter": "django_components.component_formatter"
|
||||
)
|
||||
```
|
||||
|
||||
**Examples:**
|
||||
|
||||
- `"django_components.component_formatter"`
|
||||
|
||||
Set
|
||||
|
||||
```python
|
||||
COMPONENTS = ComponentsSettings(
|
||||
"tag_formatter": "django_components.component_formatter"
|
||||
)
|
||||
```
|
||||
|
||||
To write components like this:
|
||||
|
||||
```django
|
||||
{% component "button" href="..." %}
|
||||
Click me!
|
||||
{% endcomponent %}
|
||||
```
|
||||
|
||||
- `django_components.component_shorthand_formatter`
|
||||
|
||||
Set
|
||||
|
||||
```python
|
||||
COMPONENTS = ComponentsSettings(
|
||||
"tag_formatter": "django_components.component_shorthand_formatter"
|
||||
)
|
||||
```
|
||||
|
||||
To write components like this:
|
||||
|
||||
```django
|
||||
{% button href="..." %}
|
||||
Click me!
|
||||
{% endbutton %}
|
||||
```
|
||||
"""
|
||||
|
||||
template_cache_size: Optional[int] = None
|
||||
"""
|
||||
Configure the maximum amount of Django templates to be cached.
|
||||
|
||||
Defaults to `128`.
|
||||
|
||||
Each time a [Django template](https://docs.djangoproject.com/en/5.1/ref/templates/api/#django.template.Template)
|
||||
is rendered, it is cached to a global in-memory cache (using Python's
|
||||
[`lru_cache`](https://docs.python.org/3/library/functools.html#functools.lru_cache)
|
||||
decorator). This speeds up the next render of the component.
|
||||
As the same component is often used many times on the same page, these savings add up.
|
||||
|
||||
By default the cache holds 128 component templates in memory, which should be enough for most sites.
|
||||
But if you have a lot of components, or if you are overriding
|
||||
[`Component.get_template()`](../api#django_components.Component.get_template)
|
||||
to render many dynamic templates, you can increase this number.
|
||||
|
||||
```python
|
||||
COMPONENTS = ComponentsSettings(
|
||||
template_cache_size=256,
|
||||
)
|
||||
```
|
||||
|
||||
To remove the cache limit altogether and cache everything, set `template_cache_size` to `None`.
|
||||
|
||||
```python
|
||||
COMPONENTS = ComponentsSettings(
|
||||
template_cache_size=None,
|
||||
)
|
||||
```
|
||||
|
||||
If you want to add templates to the cache yourself, you can use
|
||||
[`cached_template()`](../api/#django_components.cached_template):
|
||||
|
||||
```python
|
||||
from django_components import cached_template
|
||||
|
||||
cached_template("Variable: {{ variable }}")
|
||||
|
||||
# You can optionally specify Template class, and other Template inputs:
|
||||
class MyTemplate(Template):
|
||||
pass
|
||||
|
||||
cached_template(
|
||||
"Variable: {{ variable }}",
|
||||
template_cls=MyTemplate,
|
||||
name=...
|
||||
origin=...
|
||||
engine=...
|
||||
)
|
||||
```
|
||||
"""
|
||||
|
||||
|
||||
# NOTE: Some defaults depend on the Django settings, which may not yet be
|
||||
# initialized at the time that these settings are generated. For such cases
|
||||
# we define the defaults as a factory function, and use the `Dynamic` class to
|
||||
# mark such fields.
|
||||
@dataclass(frozen=True)
|
||||
class Dynamic(Generic[T]):
|
||||
getter: Callable[[], T]
|
||||
|
||||
|
||||
# This is the source of truth for the settings defaults. If the documentation
|
||||
# does NOT match it, the documentation should be updated.
|
||||
#
|
||||
# NOTE: Because we need to access Django settings to generate default dirs
|
||||
# for `COMPONENTS.dirs`, we do it lazily.
|
||||
# NOTE 2: We show the defaults in the documentation, together with the comments
|
||||
# (except for the `Dynamic` instances and comments like `type: ignore`).
|
||||
# So `fmt: off` turns off Black formatting and `snippet:defaults` allows
|
||||
# us to extract the snippet from the file.
|
||||
#
|
||||
# fmt: off
|
||||
# --snippet:defaults--
|
||||
defaults = ComponentsSettings(
|
||||
autodiscover=True,
|
||||
context_behavior=ContextBehavior.DJANGO.value, # "django" | "isolated"
|
||||
# Root-level "components" dirs, e.g. `/path/to/proj/components/`
|
||||
dirs=Dynamic(lambda: [Path(settings.BASE_DIR) / "components"]), # type: ignore[arg-type]
|
||||
# App-level "components" dirs, e.g. `[app]/components/`
|
||||
app_dirs=["components"],
|
||||
dynamic_component_name="dynamic",
|
||||
libraries=[], # E.g. ["mysite.components.forms", ...]
|
||||
multiline_tags=True,
|
||||
reload_on_file_change=False,
|
||||
static_files_allowed=[
|
||||
".css",
|
||||
".js", ".jsx", ".ts", ".tsx",
|
||||
# Images
|
||||
".apng", ".png", ".avif", ".gif", ".jpg",
|
||||
".jpeg", ".jfif", ".pjpeg", ".pjp", ".svg",
|
||||
".webp", ".bmp", ".ico", ".cur", ".tif", ".tiff",
|
||||
# Fonts
|
||||
".eot", ".ttf", ".woff", ".otf", ".svg",
|
||||
],
|
||||
static_files_forbidden=[
|
||||
# See https://marketplace.visualstudio.com/items?itemName=junstyle.vscode-django-support
|
||||
".html", ".django", ".dj", ".tpl",
|
||||
# Python files
|
||||
".py", ".pyc",
|
||||
],
|
||||
tag_formatter="django_components.component_formatter",
|
||||
template_cache_size=128,
|
||||
)
|
||||
# --endsnippet:defaults--
|
||||
# fmt: on
|
||||
|
||||
|
||||
class InternalSettings:
|
||||
@property
|
||||
def settings(self) -> Dict:
|
||||
return getattr(settings, "COMPONENTS", {})
|
||||
def _settings(self) -> ComponentsSettings:
|
||||
data = getattr(settings, "COMPONENTS", {})
|
||||
return ComponentsSettings(**data) if not isinstance(data, ComponentsSettings) else data
|
||||
|
||||
@property
|
||||
def AUTODISCOVER(self) -> bool:
|
||||
return self.settings.get("autodiscover", True)
|
||||
return default(self._settings.autodiscover, cast(bool, defaults.autodiscover))
|
||||
|
||||
@property
|
||||
def DIRS(self) -> List[Union[str, Tuple[str, str]]]:
|
||||
base_dir_path = Path(settings.BASE_DIR)
|
||||
return self.settings.get("dirs", [base_dir_path / "components"])
|
||||
def DIRS(self) -> Sequence[Union[str, PathLike, Tuple[str, str], Tuple[str, PathLike]]]:
|
||||
# For DIRS we use a getter, because default values uses Django settings,
|
||||
# which may not yet be initialized at the time these settings are generated.
|
||||
default_fn = cast(Dynamic[Sequence[Union[str, Tuple[str, str]]]], defaults.dirs)
|
||||
default_dirs = default_fn.getter()
|
||||
return default(self._settings.dirs, default_dirs)
|
||||
|
||||
@property
|
||||
def APP_DIRS(self) -> List[str]:
|
||||
return self.settings.get("app_dirs", ["components"])
|
||||
def APP_DIRS(self) -> Sequence[str]:
|
||||
return default(self._settings.app_dirs, cast(List[str], defaults.app_dirs))
|
||||
|
||||
@property
|
||||
def DYNAMIC_COMPONENT_NAME(self) -> str:
|
||||
return self.settings.get("dynamic_component_name", "dynamic")
|
||||
return default(self._settings.dynamic_component_name, cast(str, defaults.dynamic_component_name))
|
||||
|
||||
@property
|
||||
def LIBRARIES(self) -> List[str]:
|
||||
return self.settings.get("libraries", [])
|
||||
return default(self._settings.libraries, cast(List[str], defaults.libraries))
|
||||
|
||||
@property
|
||||
def MULTILINE_TAGS(self) -> bool:
|
||||
return self.settings.get("multiline_tags", True)
|
||||
return default(self._settings.multiline_tags, cast(bool, defaults.multiline_tags))
|
||||
|
||||
@property
|
||||
def RELOAD_ON_TEMPLATE_CHANGE(self) -> bool:
|
||||
return self.settings.get("reload_on_template_change", False)
|
||||
def RELOAD_ON_FILE_CHANGE(self) -> bool:
|
||||
val = self._settings.reload_on_file_change
|
||||
# TODO_REMOVE_IN_V1
|
||||
if val is None:
|
||||
val = self._settings.reload_on_template_change
|
||||
|
||||
return default(val, cast(bool, defaults.reload_on_file_change))
|
||||
|
||||
@property
|
||||
def TEMPLATE_CACHE_SIZE(self) -> int:
|
||||
return self.settings.get("template_cache_size", 128)
|
||||
return default(self._settings.template_cache_size, cast(int, defaults.template_cache_size))
|
||||
|
||||
@property
|
||||
def STATIC_FILES_ALLOWED(self) -> List[Union[str, re.Pattern]]:
|
||||
default_static_files = [
|
||||
".css",
|
||||
".js",
|
||||
# Images - See https://developer.mozilla.org/en-US/docs/Web/Media/Formats/Image_types#common_image_file_types # noqa: E501
|
||||
".apng",
|
||||
".png",
|
||||
".avif",
|
||||
".gif",
|
||||
".jpg",
|
||||
".jpeg",
|
||||
".jfif",
|
||||
".pjpeg",
|
||||
".pjp",
|
||||
".svg",
|
||||
".webp",
|
||||
".bmp",
|
||||
".ico",
|
||||
".cur",
|
||||
".tif",
|
||||
".tiff",
|
||||
# Fonts - See https://stackoverflow.com/q/30572159/9788634
|
||||
".eot",
|
||||
".ttf",
|
||||
".woff",
|
||||
".otf",
|
||||
".svg",
|
||||
]
|
||||
return self.settings.get("static_files_allowed", default_static_files)
|
||||
def STATIC_FILES_ALLOWED(self) -> Sequence[Union[str, re.Pattern]]:
|
||||
return default(self._settings.static_files_allowed, cast(List[str], defaults.static_files_allowed))
|
||||
|
||||
@property
|
||||
def STATIC_FILES_FORBIDDEN(self) -> List[Union[str, re.Pattern]]:
|
||||
default_forbidden_static_files = [
|
||||
".html",
|
||||
# See https://marketplace.visualstudio.com/items?itemName=junstyle.vscode-django-support
|
||||
".django",
|
||||
".dj",
|
||||
".tpl",
|
||||
# Python files
|
||||
".py",
|
||||
".pyc",
|
||||
]
|
||||
return self.settings.get("forbidden_static_files", default_forbidden_static_files)
|
||||
def STATIC_FILES_FORBIDDEN(self) -> Sequence[Union[str, re.Pattern]]:
|
||||
val = self._settings.static_files_forbidden
|
||||
# TODO_REMOVE_IN_V1
|
||||
if val is None:
|
||||
val = self._settings.forbidden_static_files
|
||||
|
||||
return default(val, cast(List[str], defaults.static_files_forbidden))
|
||||
|
||||
@property
|
||||
def CONTEXT_BEHAVIOR(self) -> ContextBehavior:
|
||||
raw_value = self.settings.get("context_behavior", ContextBehavior.DJANGO.value)
|
||||
raw_value = cast(str, default(self._settings.context_behavior, defaults.context_behavior))
|
||||
return self._validate_context_behavior(raw_value)
|
||||
|
||||
def _validate_context_behavior(self, raw_value: ContextBehavior) -> ContextBehavior:
|
||||
def _validate_context_behavior(self, raw_value: Union[ContextBehavior, str]) -> ContextBehavior:
|
||||
try:
|
||||
return ContextBehavior(raw_value)
|
||||
except ValueError:
|
||||
|
@ -188,7 +695,8 @@ class AppSettings:
|
|||
|
||||
@property
|
||||
def TAG_FORMATTER(self) -> Union["TagFormatterABC", str]:
|
||||
return self.settings.get("tag_formatter", "django_components.component_formatter")
|
||||
tag_formatter = default(self._settings.tag_formatter, cast(str, defaults.tag_formatter))
|
||||
return cast(Union["TagFormatterABC", str], tag_formatter)
|
||||
|
||||
|
||||
app_settings = AppSettings()
|
||||
app_settings = InternalSettings()
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue