10 KiB
New in version 0.131
Django-components functionality can be extended with "extensions". Extensions allow for powerful customization and integrations. They can:
- Tap into lifecycle events, such as when a component is created, deleted, registered, or unregistered.
- Add new attributes and methods to the components under an extension-specific nested class.
Setting up extensions
Extensions are configured in the Django settings under COMPONENTS.extensions
.
Extensions can be set by either as an import string or by passing in a class:
# settings.py
class MyExtension(ComponentsExtension):
name = "my_extension"
class ExtensionClass(BaseExtensionClass):
...
COMPONENTS = ComponentsSettings(
extensions=[
MyExtension,
"another_app.extensions.AnotherExtension",
"my_app.extensions.ThirdExtension",
],
)
Lifecycle hooks
Extensions can define methods to hook into lifecycle events, such as:
- Component creation or deletion
- Un/registering a component
- Creating or deleting a registry
- Pre-processing data passed to a component on render
- Post-processing data returned from
get_context_data()
and others.
See the full list in Extension Hooks Reference.
Configuring extensions per component
Each extension has a corresponding nested class within the Component
class. These allow
to configure the extensions on a per-component basis.
!!! note
**Accessing the component instance from inside the nested classes:**
Each method of the nested classes has access to the `component` attribute,
which points to the component instance.
```python
class MyTable(Component):
class View:
def get(self, request):
# `self.component` points to the instance of `MyTable` Component.
return self.component.get(request)
```
Example: Component as View
The Components as Views feature is actually implemented as an extension
that is configured by a View
nested class.
You can override the get
, post
, etc methods to customize the behavior of the component as a view:
class MyTable(Component):
class View:
def get(self, request):
return self.component.get(request)
def post(self, request):
return self.component.post(request)
...
Example: Storybook integration
The Storybook integration (work in progress) is an extension that is configured by a Storybook
nested class.
You can override methods such as title
, parameters
, etc, to customize how to generate a Storybook
JSON file from the component.
class MyTable(Component):
class Storybook:
def title(self):
return self.component.__class__.__name__
def parameters(self) -> Parameters:
return {
"server": {
"id": self.component.__class__.__name__,
}
}
def stories(self) -> List[StoryAnnotations]:
return []
...
Accessing extensions in components
Above, we've configured extensions View
and Storybook
for the MyTable
component.
You can access the instances of these extension classes in the component instance.
For example, the View extension is available as self.view
:
class MyTable(Component):
def get_context_data(self, request):
# `self.view` points to the instance of `View` extension.
return {
"view": self.view,
}
And the Storybook extension is available as self.storybook
:
class MyTable(Component):
def get_context_data(self, request):
# `self.storybook` points to the instance of `Storybook` extension.
return {
"title": self.storybook.title(),
}
Thus, you can use extensions to add methods or attributes that will be available to all components in their component context.
Writing extensions
Creating extensions in django-components involves defining a class that inherits from
ComponentExtension
.
This class can implement various lifecycle hooks and define new attributes or methods to be added to components.
Defining an extension
To create an extension, define a class that inherits from ComponentExtension
and implement the desired hooks.
- Each extension MUST have a
name
attribute. The name MUST be a valid Python identifier. - The extension MAY implement any of the hook methods.
- Each hook method receives a context object with relevant data.
from django_components.extension import ComponentExtension, OnComponentClassCreatedContext
class MyExtension(ComponentExtension):
name = "my_extension"
def on_component_class_created(self, ctx: OnComponentClassCreatedContext) -> None:
# Custom logic for when a component class is created
ctx.component_cls.my_attr = "my_value"
Defining the extension class
In previous sections we've seen the View
and Storybook
extensions classes that were nested within the Component
class:
class MyComponent(Component):
class View:
...
class Storybook:
...
These can be understood as component-specific overrides or configuration.
The nested extension classes like View
or Storybook
will actually subclass from a base extension
class as defined on the ComponentExtension.ExtensionClass
.
This is how extensions define the "default" behavior of their nested extension classes.
For example, the View
base extension class defines the handlers for GET, POST, etc:
from django_components.extension import ComponentExtension, BaseExtensionClass
class ViewExtension(ComponentExtension):
name = "view"
# The default behavior of the `View` extension class.
class ExtensionClass(BaseExtensionClass):
def get(self, request):
return self.component.get(request)
def post(self, request):
return self.component.post(request)
...
In any component that then defines a nested View
extension class, the View
extension class will actually
subclass from the ViewExtension.ExtensionClass
class.
In other words, when you define a component like this:
class MyTable(Component):
class View:
def get(self, request):
# Do something
...
It will actually be implemented as if the View
class subclassed from base class ViewExtension.ExtensionClass
:
class MyTable(Component):
class View(ViewExtension.ExtensionClass):
def get(self, request):
# Do something
...
!!! warning
When writing an extension, the `ExtensionClass` MUST subclass the base class [`BaseExtensionClass`](../../../reference/api/#django_components.ComponentExtension.BaseExtensionClass).
This base class ensures that the extension class will have access to the component instance.
Registering extensions
Once the extension is defined, it needs to be registered in the Django settings to be used by the application.
Extensions can be given either as an extension class, or its import string:
# settings.py
COMPONENTS = {
"extensions": [
"my_app.extensions.MyExtension",
],
}
Or by reference:
# settings.py
from my_app.extensions import MyExtension
COMPONENTS = {
"extensions": [
MyExtension,
],
}
Full example: Custom logging extension
To tie it all together, here's an example of a custom logging extension that logs when components are created, deleted, or rendered:
- Each component can specify which color to use for the logging by setting
Component.ColorLogger.color
. - The extension will log the component name and color when the component is created, deleted, or rendered.
from django_components.extension import (
ComponentExtension,
OnComponentClassCreatedContext,
OnComponentClassDeletedContext,
OnComponentInputContext,
)
class ColorLoggerExtensionClass(BaseExtensionClass):
color: str
class ColorLoggerExtension(ComponentExtension):
name = "color_logger"
# All `Component.ColorLogger` classes will inherit from this class.
ExtensionClass = ColorLoggerExtensionClass
# These hooks don't have access to the Component instance, only to the Component class,
# so we access the color as `Component.ColorLogger.color`.
def on_component_class_created(self, ctx: OnComponentClassCreatedContext) -> None:
log.info(
f"Component {ctx.component_cls} created.",
color=ctx.component_cls.ColorLogger.color,
)
def on_component_class_deleted(self, ctx: OnComponentClassDeletedContext) -> None:
log.info(
f"Component {ctx.component_cls} deleted.",
color=ctx.component_cls.ColorLogger.color,
)
# This hook has access to the Component instance, so we access the color
# as `self.component.color_logger.color`.
def on_component_input(self, ctx: OnComponentInputContext) -> None:
log.info(
f"Rendering component {ctx.component_cls}.",
color=ctx.component.color_logger.color,
)
To use the ColorLoggerExtension
, add it to your settings:
# settings.py
COMPONENTS = {
"extensions": [
ColorLoggerExtension,
],
}
Once registered, in any component, you can define a ColorLogger
attribute:
class MyComponent(Component):
class ColorLogger:
color = "red"
This will log the component name and color when the component is created, deleted, or rendered.