mirror of
https://github.com/django-components/django-components.git
synced 2025-11-17 21:57:07 +00:00
feat: extension defaults + docs + API cleanup (#1215)
This commit is contained in:
parent
7df8019544
commit
bb129aefab
16 changed files with 858 additions and 144 deletions
|
|
@ -36,6 +36,7 @@ from django_components.components import DynamicComponent
|
|||
from django_components.dependencies import DependenciesStrategy, render_dependencies
|
||||
from django_components.extension import (
|
||||
ComponentExtension,
|
||||
ExtensionComponentConfig,
|
||||
OnComponentRegisteredContext,
|
||||
OnComponentUnregisteredContext,
|
||||
OnRegistryCreatedContext,
|
||||
|
|
@ -47,6 +48,7 @@ from django_components.extension import (
|
|||
)
|
||||
from django_components.extensions.cache import ComponentCache
|
||||
from django_components.extensions.defaults import ComponentDefaults, Default
|
||||
from django_components.extensions.debug_highlight import ComponentDebugHighlight
|
||||
from django_components.extensions.view import ComponentView, get_component_url
|
||||
from django_components.library import TagProtectedError
|
||||
from django_components.node import BaseNode, template_tag
|
||||
|
|
@ -93,6 +95,7 @@ __all__ = [
|
|||
"Component",
|
||||
"ComponentCache",
|
||||
"ComponentCommand",
|
||||
"ComponentDebugHighlight",
|
||||
"ComponentDefaults",
|
||||
"ComponentExtension",
|
||||
"ComponentFileEntry",
|
||||
|
|
@ -111,6 +114,7 @@ __all__ = [
|
|||
"DependenciesStrategy",
|
||||
"DynamicComponent",
|
||||
"Empty",
|
||||
"ExtensionComponentConfig",
|
||||
"format_attributes",
|
||||
"get_component_by_class_id",
|
||||
"get_component_dirs",
|
||||
|
|
|
|||
|
|
@ -158,6 +158,10 @@ class ComponentsSettings(NamedTuple):
|
|||
- Python import path, e.g. `"path.to.my_extension.MyExtension"`.
|
||||
- Extension class, e.g. `my_extension.MyExtension`.
|
||||
|
||||
Read more about [extensions](../../concepts/advanced/extensions).
|
||||
|
||||
**Example:**
|
||||
|
||||
```python
|
||||
COMPONENTS = ComponentsSettings(
|
||||
extensions=[
|
||||
|
|
@ -168,6 +172,29 @@ class ComponentsSettings(NamedTuple):
|
|||
```
|
||||
"""
|
||||
|
||||
extensions_defaults: Optional[Dict[str, Any]] = None
|
||||
"""
|
||||
Global defaults for the extension classes.
|
||||
|
||||
Read more about [Extension defaults](../../concepts/advanced/extensions#extension-defaults).
|
||||
|
||||
**Example:**
|
||||
|
||||
```python
|
||||
COMPONENTS = ComponentsSettings(
|
||||
extensions_defaults={
|
||||
"my_extension": {
|
||||
"my_setting": "my_value",
|
||||
},
|
||||
"cache": {
|
||||
"enabled": True,
|
||||
"ttl": 60,
|
||||
},
|
||||
},
|
||||
)
|
||||
```
|
||||
"""
|
||||
|
||||
autodiscover: Optional[bool] = None
|
||||
"""
|
||||
Toggle whether to run [autodiscovery](../../concepts/fundamentals/autodiscovery) at the Django server startup.
|
||||
|
|
@ -282,8 +309,13 @@ class ComponentsSettings(NamedTuple):
|
|||
> [here](https://github.com/django-components/django-components/issues/498).
|
||||
"""
|
||||
|
||||
# TODO_v1 - remove. Users should use extension defaults instead.
|
||||
debug_highlight_components: Optional[bool] = None
|
||||
"""
|
||||
DEPRECATED. Use
|
||||
[`extensions_defaults`](../settings/#django_components.app_settings.ComponentsSettings.extensions_defaults)
|
||||
instead. Will be removed in v1.
|
||||
|
||||
Enable / disable component highlighting.
|
||||
See [Troubleshooting](../../guides/other/troubleshooting#component-highlighting) for more details.
|
||||
|
||||
|
|
@ -296,8 +328,13 @@ class ComponentsSettings(NamedTuple):
|
|||
```
|
||||
"""
|
||||
|
||||
# TODO_v1 - remove. Users should use extension defaults instead.
|
||||
debug_highlight_slots: Optional[bool] = None
|
||||
"""
|
||||
DEPRECATED. Use
|
||||
[`extensions_defaults`](../settings/#django_components.app_settings.ComponentsSettings.extensions_defaults)
|
||||
instead. Will be removed in v1.
|
||||
|
||||
Enable / disable slot highlighting.
|
||||
See [Troubleshooting](../../guides/other/troubleshooting#slot-highlighting) for more details.
|
||||
|
||||
|
|
@ -670,6 +707,7 @@ defaults = ComponentsSettings(
|
|||
debug_highlight_slots=False,
|
||||
dynamic_component_name="dynamic",
|
||||
extensions=[],
|
||||
extensions_defaults={},
|
||||
libraries=[], # E.g. ["mysite.components.forms", ...]
|
||||
multiline_tags=True,
|
||||
reload_on_file_change=False,
|
||||
|
|
@ -735,6 +773,7 @@ class InternalSettings:
|
|||
# NOTE: Internally we store the extensions as a list of instances, but the user
|
||||
# can pass in either a list of classes or a list of import strings.
|
||||
extensions=self._prepare_extensions(components_settings), # type: ignore[arg-type]
|
||||
extensions_defaults=default(components_settings.extensions_defaults, defaults.extensions_defaults),
|
||||
multiline_tags=default(components_settings.multiline_tags, defaults.multiline_tags),
|
||||
reload_on_file_change=self._prepare_reload_on_file_change(components_settings),
|
||||
template_cache_size=default(components_settings.template_cache_size, defaults.template_cache_size),
|
||||
|
|
@ -755,7 +794,15 @@ class InternalSettings:
|
|||
from django_components.extensions.defaults import DefaultsExtension
|
||||
from django_components.extensions.view import ViewExtension
|
||||
|
||||
extensions = [CacheExtension, DefaultsExtension, ViewExtension, DebugHighlightExtension] + list(extensions)
|
||||
extensions = cast(
|
||||
List[Type["ComponentExtension"]],
|
||||
[
|
||||
CacheExtension,
|
||||
DefaultsExtension,
|
||||
ViewExtension,
|
||||
DebugHighlightExtension,
|
||||
],
|
||||
) + list(extensions)
|
||||
|
||||
# Extensions may be passed in either as classes or import strings.
|
||||
extension_instances: List["ComponentExtension"] = []
|
||||
|
|
|
|||
|
|
@ -56,6 +56,7 @@ from django_components.extension import (
|
|||
extensions,
|
||||
)
|
||||
from django_components.extensions.cache import ComponentCache
|
||||
from django_components.extensions.debug_highlight import ComponentDebugHighlight
|
||||
from django_components.extensions.defaults import ComponentDefaults
|
||||
from django_components.extensions.view import ComponentView, ViewFn
|
||||
from django_components.node import BaseNode
|
||||
|
|
@ -1727,6 +1728,13 @@ class Component(metaclass=ComponentMeta):
|
|||
"""
|
||||
Instance of [`ComponentView`](../api#django_components.ComponentView) available at component render time.
|
||||
"""
|
||||
DebugHighlight: ClassVar[Type[ComponentDebugHighlight]]
|
||||
"""
|
||||
The fields of this class are used to configure the component debug highlighting.
|
||||
|
||||
Read more about [Component debug highlighting](../../guides/other/troubleshooting#component-and-slot-highlighting).
|
||||
"""
|
||||
debug_highlight: ComponentDebugHighlight
|
||||
|
||||
# #####################################
|
||||
# MISC
|
||||
|
|
|
|||
|
|
@ -1,5 +1,19 @@
|
|||
from functools import wraps
|
||||
from typing import TYPE_CHECKING, Any, Callable, Dict, List, NamedTuple, Optional, Set, Tuple, Type, TypeVar, Union
|
||||
from typing import (
|
||||
TYPE_CHECKING,
|
||||
Any,
|
||||
Callable,
|
||||
ClassVar,
|
||||
Dict,
|
||||
List,
|
||||
NamedTuple,
|
||||
Optional,
|
||||
Set,
|
||||
Tuple,
|
||||
Type,
|
||||
TypeVar,
|
||||
Union,
|
||||
)
|
||||
|
||||
import django.urls
|
||||
from django.template import Context
|
||||
|
|
@ -154,43 +168,159 @@ class OnSlotRenderedContext(NamedTuple):
|
|||
################################################
|
||||
|
||||
|
||||
class BaseExtensionClass:
|
||||
"""Base class for all extension classes."""
|
||||
class ExtensionComponentConfig:
|
||||
"""
|
||||
`ExtensionComponentConfig` is the base class for all extension component configs.
|
||||
|
||||
Extensions can define nested classes on the component class,
|
||||
such as [`Component.View`](../api#django_components.Component.View) or
|
||||
[`Component.Cache`](../api#django_components.Component.Cache):
|
||||
|
||||
```py
|
||||
class MyComp(Component):
|
||||
class View:
|
||||
def get(self, request):
|
||||
...
|
||||
|
||||
class Cache:
|
||||
ttl = 60
|
||||
```
|
||||
|
||||
This allows users to configure extension behavior per component.
|
||||
|
||||
Behind the scenes, the nested classes that users define on their components
|
||||
are merged with the extension's "base" class.
|
||||
|
||||
So the example above is the same as:
|
||||
|
||||
```py
|
||||
class MyComp(Component):
|
||||
class View(ViewExtension.ComponentConfig):
|
||||
def get(self, request):
|
||||
...
|
||||
|
||||
class Cache(CacheExtension.ComponentConfig):
|
||||
ttl = 60
|
||||
```
|
||||
|
||||
Where both `ViewExtension.ComponentConfig` and `CacheExtension.ComponentConfig` are
|
||||
subclasses of `ExtensionComponentConfig`.
|
||||
"""
|
||||
|
||||
component_cls: Type["Component"]
|
||||
"""The [`Component`](../api#django_components.Component) class that this extension is defined on."""
|
||||
|
||||
# TODO_v1 - Remove, superseded by `component_cls`
|
||||
component_class: Type["Component"]
|
||||
"""The Component class that this extension is defined on."""
|
||||
"""The [`Component`](../api#django_components.Component) class that this extension is defined on."""
|
||||
|
||||
component: "Component"
|
||||
"""
|
||||
When a [`Component`](../api#django_components.Component) is instantiated,
|
||||
also the nested extension classes (such as `Component.View`) are instantiated,
|
||||
receiving the component instance as an argument.
|
||||
|
||||
This attribute holds the owner [`Component`](../api#django_components.Component) instance
|
||||
that this extension is defined on.
|
||||
"""
|
||||
|
||||
def __init__(self, component: "Component") -> None:
|
||||
self.component = component
|
||||
|
||||
|
||||
# TODO_v1 - Delete
|
||||
BaseExtensionClass = ExtensionComponentConfig
|
||||
"""
|
||||
Deprecated. Will be removed in v1.0. Use
|
||||
[`ComponentConfig`](../api#django_components.ExtensionComponentConfig) instead.
|
||||
"""
|
||||
|
||||
|
||||
# TODO_V1 - Delete, meta class was needed only for backwards support for ExtensionClass.
|
||||
class ExtensionMeta(type):
|
||||
def __new__(mcs, name: Any, bases: Tuple, attrs: Dict) -> Any:
|
||||
# Rename `ExtensionClass` to `ComponentConfig`
|
||||
if "ExtensionClass" in attrs:
|
||||
attrs["ComponentConfig"] = attrs.pop("ExtensionClass")
|
||||
|
||||
return super().__new__(mcs, name, bases, attrs)
|
||||
|
||||
|
||||
# NOTE: This class is used for generating documentation for the extension hooks API.
|
||||
# To be recognized, all hooks must start with `on_` prefix.
|
||||
class ComponentExtension:
|
||||
class ComponentExtension(metaclass=ExtensionMeta):
|
||||
"""
|
||||
Base class for all extensions.
|
||||
|
||||
Read more on [Extensions](../../concepts/advanced/extensions).
|
||||
|
||||
**Example:**
|
||||
|
||||
```python
|
||||
class ExampleExtension(ComponentExtension):
|
||||
name = "example"
|
||||
|
||||
# Component-level behavior and settings. User will be able to override
|
||||
# the attributes and methods defined here on the component classes.
|
||||
class ComponentConfig(ComponentExtension.ComponentConfig):
|
||||
foo = "1"
|
||||
bar = "2"
|
||||
|
||||
def baz(cls):
|
||||
return "3"
|
||||
|
||||
# URLs
|
||||
urls = [
|
||||
URLRoute(path="dummy-view/", handler=dummy_view, name="dummy"),
|
||||
URLRoute(path="dummy-view-2/<int:id>/<str:name>/", handler=dummy_view_2, name="dummy-2"),
|
||||
]
|
||||
|
||||
# Commands
|
||||
commands = [
|
||||
HelloWorldCommand,
|
||||
]
|
||||
|
||||
# Hooks
|
||||
def on_component_class_created(self, ctx: OnComponentClassCreatedContext) -> None:
|
||||
print(ctx.component_cls.__name__)
|
||||
|
||||
def on_component_class_deleted(self, ctx: OnComponentClassDeletedContext) -> None:
|
||||
print(ctx.component_cls.__name__)
|
||||
```
|
||||
|
||||
Which users then can override on a per-component basis. E.g.:
|
||||
|
||||
```python
|
||||
class MyComp(Component):
|
||||
class Example:
|
||||
foo = "overridden"
|
||||
|
||||
def baz(self):
|
||||
return "overridden baz"
|
||||
```
|
||||
"""
|
||||
|
||||
###########################
|
||||
# USER INPUT
|
||||
###########################
|
||||
|
||||
name: str
|
||||
name: ClassVar[str]
|
||||
"""
|
||||
Name of the extension.
|
||||
|
||||
Name must be lowercase, and must be a valid Python identifier (e.g. `"my_extension"`).
|
||||
|
||||
The extension may add new features to the [`Component`](../api#django_components.Component)
|
||||
class by allowing users to define and access a nested class in the `Component` class.
|
||||
class by allowing users to define and access a nested class in
|
||||
the [`Component`](../api#django_components.Component) class.
|
||||
|
||||
The extension name determines the name of the nested class in the `Component` class, and the attribute
|
||||
The extension name determines the name of the nested class in
|
||||
the [`Component`](../api#django_components.Component) class, and the attribute
|
||||
under which the extension will be accessible.
|
||||
|
||||
E.g. if the extension name is `"my_extension"`, then the nested class in the `Component` class
|
||||
will be `MyExtension`, and the extension will be accessible as `MyComp.my_extension`.
|
||||
E.g. if the extension name is `"my_extension"`, then the nested class in
|
||||
the [`Component`](../api#django_components.Component) class will be
|
||||
`MyExtension`, and the extension will be accessible as `MyComp.my_extension`.
|
||||
|
||||
```python
|
||||
class MyComp(Component):
|
||||
|
|
@ -202,13 +332,20 @@ class ComponentExtension:
|
|||
"my_extension": self.my_extension.do_something(),
|
||||
}
|
||||
```
|
||||
|
||||
!!! info
|
||||
|
||||
The extension class name can be customized by setting
|
||||
the [`class_name`](../api#django_components.ComponentExtension.class_name) attribute.
|
||||
"""
|
||||
|
||||
class_name: str
|
||||
class_name: ClassVar[str]
|
||||
"""
|
||||
Name of the extension class.
|
||||
|
||||
By default, this is the same as `name`, but with snake_case converted to PascalCase.
|
||||
By default, this is set automatically at class creation. The class name is the same as
|
||||
the [`name`](../api#django_components.ComponentExtension.name) attribute, but with snake_case
|
||||
converted to PascalCase.
|
||||
|
||||
So if the extension name is `"my_extension"`, then the extension class name will be `"MyExtension"`.
|
||||
|
||||
|
|
@ -217,20 +354,41 @@ class ComponentExtension:
|
|||
class MyExtension: # <--- This is the extension class
|
||||
...
|
||||
```
|
||||
|
||||
To customize the class name, you can manually set the `class_name` attribute.
|
||||
|
||||
The class name must be a valid Python identifier.
|
||||
|
||||
**Example:**
|
||||
|
||||
```python
|
||||
class MyExt(ComponentExtension):
|
||||
name = "my_extension"
|
||||
class_name = "MyCustomExtension"
|
||||
```
|
||||
|
||||
This will make the extension class name `"MyCustomExtension"`.
|
||||
|
||||
```python
|
||||
class MyComp(Component):
|
||||
class MyCustomExtension: # <--- This is the extension class
|
||||
...
|
||||
```
|
||||
"""
|
||||
|
||||
ExtensionClass = BaseExtensionClass
|
||||
ComponentConfig: ClassVar[Type[ExtensionComponentConfig]] = ExtensionComponentConfig
|
||||
"""
|
||||
Base class that the "extension class" nested within a [`Component`](../api#django_components.Component)
|
||||
class will inherit from.
|
||||
Base class that the "component-level" extension config nested within
|
||||
a [`Component`](../api#django_components.Component) class will inherit from.
|
||||
|
||||
This is where you can define new methods and attributes that will be available to the component
|
||||
instance.
|
||||
|
||||
Background:
|
||||
|
||||
The extension may add new features to the `Component` class by allowing users to
|
||||
define and access a nested class in the `Component` class. E.g.:
|
||||
The extension may add new features to the [`Component`](../api#django_components.Component) class
|
||||
by allowing users to define and access a nested class in
|
||||
the [`Component`](../api#django_components.Component) class. E.g.:
|
||||
|
||||
```python
|
||||
class MyComp(Component):
|
||||
|
|
@ -243,19 +401,20 @@ class ComponentExtension:
|
|||
}
|
||||
```
|
||||
|
||||
When rendering a component, the nested extension class will be set as a subclass of `ExtensionClass`.
|
||||
So it will be same as if the user had directly inherited from `ExtensionClass`. E.g.:
|
||||
When rendering a component, the nested extension class will be set as a subclass of
|
||||
`ComponentConfig`. So it will be same as if the user had directly inherited from extension's
|
||||
`ComponentConfig`. E.g.:
|
||||
|
||||
```python
|
||||
class MyComp(Component):
|
||||
class MyExtension(ComponentExtension.ExtensionClass):
|
||||
class MyExtension(ComponentExtension.ComponentConfig):
|
||||
...
|
||||
```
|
||||
|
||||
This setting decides what the extension class will inherit from.
|
||||
"""
|
||||
|
||||
commands: List[Type[ComponentCommand]] = []
|
||||
commands: ClassVar[List[Type[ComponentCommand]]] = []
|
||||
"""
|
||||
List of commands that can be run by the extension.
|
||||
|
||||
|
|
@ -315,7 +474,7 @@ class ComponentExtension:
|
|||
```
|
||||
"""
|
||||
|
||||
urls: List[URLRoute] = []
|
||||
urls: ClassVar[List[URLRoute]] = []
|
||||
|
||||
###########################
|
||||
# Misc
|
||||
|
|
@ -629,19 +788,19 @@ class ExtensionManager:
|
|||
for extension in self.extensions:
|
||||
ext_class_name = extension.class_name
|
||||
|
||||
# If a Component class has an extension class, e.g.
|
||||
# If a Component class has a nested extension class, e.g.
|
||||
# ```python
|
||||
# class MyComp(Component):
|
||||
# class MyExtension:
|
||||
# ...
|
||||
# ```
|
||||
# then create a dummy class to make `MyComp.MyExtension` extend
|
||||
# the base class `extension.ExtensionClass`.
|
||||
# the base class `extension.ComponentConfig`.
|
||||
#
|
||||
# So it will be same as if the user had directly inherited from `extension.ExtensionClass`.
|
||||
# So it will be same as if the user had directly inherited from `extension.ComponentConfig`.
|
||||
# ```python
|
||||
# class MyComp(Component):
|
||||
# class MyExtension(MyExtension.ExtensionClass):
|
||||
# class MyExtension(MyExtension.ComponentConfig):
|
||||
# ...
|
||||
# ```
|
||||
component_ext_subclass = getattr(component_cls, ext_class_name, None)
|
||||
|
|
@ -649,11 +808,11 @@ class ExtensionManager:
|
|||
# Add escape hatch, so that user can override the extension class
|
||||
# from within the component class. E.g.:
|
||||
# ```python
|
||||
# class MyExtDifferentStillSame(MyExtension.ExtensionClass):
|
||||
# class MyExtDifferentButStillSame(MyExtension.ComponentConfig):
|
||||
# ...
|
||||
#
|
||||
# class MyComp(Component):
|
||||
# my_extension_class = MyExtDifferentStillSame
|
||||
# my_extension_class = MyExtDifferentButStillSame
|
||||
# class MyExtension:
|
||||
# ...
|
||||
# ```
|
||||
|
|
@ -661,20 +820,54 @@ class ExtensionManager:
|
|||
# Will be effectively the same as:
|
||||
# ```python
|
||||
# class MyComp(Component):
|
||||
# class MyExtension(MyExtDifferentStillSame):
|
||||
# class MyExtension(MyExtDifferentButStillSame):
|
||||
# ...
|
||||
# ```
|
||||
ext_class_override_attr = extension.name + "_class" # "my_extension_class"
|
||||
ext_base_class = getattr(component_cls, ext_class_override_attr, extension.ExtensionClass)
|
||||
ext_base_class = getattr(component_cls, ext_class_override_attr, extension.ComponentConfig)
|
||||
|
||||
# Extensions have 3 levels of configuration:
|
||||
# 1. Factory defaults - The values that the extension author set on the extension class
|
||||
# 2. User global defaults with `COMPONENTS.extensions_defaults`
|
||||
# 3. User component-level settings - The values that the user set on the component class
|
||||
#
|
||||
# The component-level settings override the global defaults, which in turn override
|
||||
# the factory defaults.
|
||||
#
|
||||
# To apply these defaults, we set them as bases for our new extension class.
|
||||
#
|
||||
# The final class will look like this:
|
||||
# ```
|
||||
# class MyExtension(MyComp.MyExtension, MyExtensionDefaults, MyExtensionBase):
|
||||
# component_cls = MyComp
|
||||
# ...
|
||||
# ```
|
||||
# Where:
|
||||
# - `MyComp.MyExtension` is the extension class that the user defined on the component class.
|
||||
# - `MyExtensionDefaults` is a dummy class that holds the extension defaults from settings.
|
||||
# - `MyExtensionBase` is the base class that the extension class inherits from.
|
||||
bases_list = [ext_base_class]
|
||||
|
||||
all_extensions_defaults = app_settings._settings.extensions_defaults or {}
|
||||
extension_defaults = all_extensions_defaults.get(extension.name, None)
|
||||
if extension_defaults:
|
||||
# Create dummy class that holds the extension defaults
|
||||
defaults_class = type(f"{ext_class_name}Defaults", tuple(), extension_defaults.copy())
|
||||
bases_list.insert(0, defaults_class)
|
||||
|
||||
if component_ext_subclass:
|
||||
bases: tuple[Type, ...] = (component_ext_subclass, ext_base_class)
|
||||
else:
|
||||
bases = (ext_base_class,)
|
||||
bases_list.insert(0, component_ext_subclass)
|
||||
|
||||
# Allow to extension class to access the owner `Component` class that via
|
||||
# `ExtensionClass.component_class`.
|
||||
component_ext_subclass = type(ext_class_name, bases, {"component_class": component_cls})
|
||||
bases: tuple[Type, ...] = tuple(bases_list)
|
||||
|
||||
# Allow component-level extension class to access the owner `Component` class that via
|
||||
# `component_cls`.
|
||||
component_ext_subclass = type(
|
||||
ext_class_name,
|
||||
bases,
|
||||
# TODO_v1 - Remove `component_class`, superseded by `component_cls`
|
||||
{"component_cls": component_cls, "component_class": component_cls},
|
||||
)
|
||||
|
||||
# Finally, reassign the new class extension class on the component class.
|
||||
setattr(component_cls, ext_class_name, component_ext_subclass)
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ from django.core.cache import BaseCache, caches
|
|||
|
||||
from django_components.extension import (
|
||||
ComponentExtension,
|
||||
ExtensionComponentConfig,
|
||||
OnComponentInputContext,
|
||||
OnComponentRenderedContext,
|
||||
)
|
||||
|
|
@ -15,7 +16,7 @@ from django_components.slots import Slot
|
|||
CACHE_KEY_PREFIX = "components:cache:"
|
||||
|
||||
|
||||
class ComponentCache(ComponentExtension.ExtensionClass): # type: ignore
|
||||
class ComponentCache(ExtensionComponentConfig):
|
||||
"""
|
||||
The interface for `Component.Cache`.
|
||||
|
||||
|
|
@ -172,7 +173,7 @@ class CacheExtension(ComponentExtension):
|
|||
|
||||
name = "cache"
|
||||
|
||||
ExtensionClass = ComponentCache
|
||||
ComponentConfig = ComponentCache
|
||||
|
||||
def __init__(self, *args: Any, **kwargs: Any):
|
||||
self.render_id_to_cache_key: dict[str, str] = {}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,12 @@
|
|||
from typing import Any, Literal, NamedTuple, Optional, Type
|
||||
|
||||
from django_components.app_settings import app_settings
|
||||
from django_components.extension import ComponentExtension, OnComponentRenderedContext, OnSlotRenderedContext
|
||||
from django_components.extension import (
|
||||
ComponentExtension,
|
||||
ExtensionComponentConfig,
|
||||
OnComponentRenderedContext,
|
||||
OnSlotRenderedContext,
|
||||
)
|
||||
from django_components.util.misc import gen_id
|
||||
|
||||
|
||||
|
|
@ -45,14 +50,6 @@ def apply_component_highlight(type: Literal["component", "slot"], output: str, n
|
|||
return output
|
||||
|
||||
|
||||
# TODO - Deprecate `DEBUG_HIGHLIGHT_SLOTS` and `DEBUG_HIGHLIGHT_COMPONENTS` (with removal in v1)
|
||||
# once `extension_defaults` is implemented.
|
||||
# That way people will be able to set the highlighting from single place.
|
||||
# At that point also document this extension in the docs:
|
||||
# - Exposing `ComponentDebugHighlight` from `__init__.py`
|
||||
# - Adding `Component.DebugHighlight` and `Component.debug_highlight` attributes to Component class
|
||||
# so it's easier to find.
|
||||
# - Check docstring of `ComponentDebugHighlight` in the docs and make sure it's correct.
|
||||
class HighlightComponentsDescriptor:
|
||||
def __get__(self, obj: Optional[Any], objtype: Type) -> bool:
|
||||
return app_settings.DEBUG_HIGHLIGHT_COMPONENTS
|
||||
|
|
@ -63,14 +60,14 @@ class HighlightSlotsDescriptor:
|
|||
return app_settings.DEBUG_HIGHLIGHT_SLOTS
|
||||
|
||||
|
||||
class ComponentDebugHighlight(ComponentExtension.ExtensionClass): # type: ignore
|
||||
class ComponentDebugHighlight(ExtensionComponentConfig):
|
||||
"""
|
||||
The interface for `Component.DebugHighlight`.
|
||||
|
||||
The fields of this class are used to configure the component debug highlighting for this component
|
||||
and its direct slots.
|
||||
|
||||
Read more about [Component debug highlighting](../../concepts/advanced/component_debug_highlighting).
|
||||
Read more about [Component debug highlighting](../../guides/other/troubleshooting#component-and-slot-highlighting).
|
||||
|
||||
**Example:**
|
||||
|
||||
|
|
@ -84,10 +81,22 @@ class ComponentDebugHighlight(ComponentExtension.ExtensionClass): # type: ignor
|
|||
```
|
||||
|
||||
To highlight ALL components and slots, set
|
||||
[`ComponentsSettings.DEBUG_HIGHLIGHT_SLOTS`](../../settings/components_settings.md#debug_highlight_slots) and
|
||||
[`ComponentsSettings.DEBUG_HIGHLIGHT_COMPONENTS`](../../settings/components_settings.md#debug_highlight_components)
|
||||
to `True`.
|
||||
"""
|
||||
[extension defaults](../../reference/settings/#django_components.app_settings.ComponentsSettings.extensions_defaults)
|
||||
in your settings:
|
||||
|
||||
```python
|
||||
from django_components import ComponentsSettings
|
||||
|
||||
COMPONENTS = ComponentsSettings(
|
||||
extensions_defaults={
|
||||
"debug_highlight": {
|
||||
"highlight_components": True,
|
||||
"highlight_slots": True,
|
||||
},
|
||||
},
|
||||
)
|
||||
```
|
||||
""" # noqa: E501
|
||||
|
||||
# TODO_v1 - Remove `DEBUG_HIGHLIGHT_COMPONENTS` and `DEBUG_HIGHLIGHT_SLOTS`
|
||||
# Instead set this as plain boolean fields.
|
||||
|
|
@ -112,7 +121,7 @@ class DebugHighlightExtension(ComponentExtension):
|
|||
"""
|
||||
|
||||
name = "debug_highlight"
|
||||
ExtensionClass = ComponentDebugHighlight
|
||||
ComponentConfig = ComponentDebugHighlight
|
||||
|
||||
# Apply highlight to the slot's rendered output
|
||||
def on_slot_rendered(self, ctx: OnSlotRenderedContext) -> Optional[str]:
|
||||
|
|
|
|||
|
|
@ -3,7 +3,12 @@ from dataclasses import MISSING, Field, dataclass
|
|||
from typing import TYPE_CHECKING, Any, Callable, Dict, List, NamedTuple, Optional, Type
|
||||
from weakref import WeakKeyDictionary
|
||||
|
||||
from django_components.extension import ComponentExtension, OnComponentClassCreatedContext, OnComponentInputContext
|
||||
from django_components.extension import (
|
||||
ComponentExtension,
|
||||
ExtensionComponentConfig,
|
||||
OnComponentClassCreatedContext,
|
||||
OnComponentInputContext,
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from django_components.component import Component
|
||||
|
|
@ -62,7 +67,8 @@ def _extract_defaults(defaults: Optional[Type]) -> List[ComponentDefaultField]:
|
|||
for default_field_key in dir(defaults):
|
||||
# Iterate only over fields set by the user (so non-dunder fields).
|
||||
# Plus ignore `component_class` because that was set by the extension system.
|
||||
if default_field_key.startswith("__") or default_field_key == "component_class":
|
||||
# TODO_V1 - Remove `component_class`
|
||||
if default_field_key.startswith("__") or default_field_key in {"component_class", "component_cls"}:
|
||||
continue
|
||||
|
||||
default_field = getattr(defaults, default_field_key)
|
||||
|
|
@ -119,7 +125,7 @@ def _apply_defaults(kwargs: Dict, defaults: List[ComponentDefaultField]) -> None
|
|||
kwargs[default_field.key] = default_value
|
||||
|
||||
|
||||
class ComponentDefaults(ComponentExtension.ExtensionClass): # type: ignore[misc,valid-type]
|
||||
class ComponentDefaults(ExtensionComponentConfig):
|
||||
"""
|
||||
The interface for `Component.Defaults`.
|
||||
|
||||
|
|
@ -164,7 +170,7 @@ class DefaultsExtension(ComponentExtension):
|
|||
"""
|
||||
|
||||
name = "defaults"
|
||||
ExtensionClass = ComponentDefaults
|
||||
ComponentConfig = ComponentDefaults
|
||||
|
||||
# Preprocess the `Component.Defaults` class, if given, so we don't have to do it
|
||||
# each time a component is rendered.
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ from django.views.generic import View
|
|||
|
||||
from django_components.extension import (
|
||||
ComponentExtension,
|
||||
ExtensionComponentConfig,
|
||||
OnComponentClassCreatedContext,
|
||||
OnComponentClassDeletedContext,
|
||||
URLRoute,
|
||||
|
|
@ -74,7 +75,7 @@ def get_component_url(
|
|||
return format_url(url, query=query, fragment=fragment)
|
||||
|
||||
|
||||
class ComponentView(ComponentExtension.ExtensionClass, View): # type: ignore
|
||||
class ComponentView(ExtensionComponentConfig, View):
|
||||
"""
|
||||
The interface for `Component.View`.
|
||||
|
||||
|
|
@ -157,7 +158,7 @@ class ComponentView(ComponentExtension.ExtensionClass, View): # type: ignore
|
|||
"""
|
||||
|
||||
def __init__(self, component: "Component", **kwargs: Any) -> None:
|
||||
ComponentExtension.ExtensionClass.__init__(self, component)
|
||||
ComponentExtension.ComponentConfig.__init__(self, component)
|
||||
View.__init__(self, **kwargs)
|
||||
|
||||
@property
|
||||
|
|
@ -257,7 +258,7 @@ class ViewExtension(ComponentExtension):
|
|||
|
||||
name = "view"
|
||||
|
||||
ExtensionClass = ComponentView
|
||||
ComponentConfig = ComponentView
|
||||
|
||||
def __init__(self) -> None:
|
||||
# Remember which route belongs to which component
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue