mirror of
https://github.com/django-components/django-components.git
synced 2025-11-17 13:52:56 +00:00
refactor: make it optional having to specify parent class of Args, Kwargs, Slots, etc
This commit is contained in:
parent
c66bd21231
commit
0255782ee2
31 changed files with 620 additions and 294 deletions
|
|
@ -74,7 +74,7 @@ and so `selected_items` will be set to `[1, 2, 3]`.
|
|||
|
||||
```py
|
||||
class ProfileCard(Component):
|
||||
class Kwargs(NamedTuple):
|
||||
class Kwargs:
|
||||
show_details: bool = True
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ Each method handles the data independently - you can define different data for t
|
|||
|
||||
```python
|
||||
class ProfileCard(Component):
|
||||
class Kwargs(NamedTuple):
|
||||
class Kwargs:
|
||||
user_id: int
|
||||
show_details: bool
|
||||
|
||||
|
|
@ -51,7 +51,7 @@ If [`get_template_data()`](../../../reference/api/#django_components.Component.g
|
|||
class ProfileCard(Component):
|
||||
template_file = "profile_card.html"
|
||||
|
||||
class Kwargs(NamedTuple):
|
||||
class Kwargs:
|
||||
user_id: int
|
||||
show_details: bool
|
||||
|
||||
|
|
@ -182,10 +182,10 @@ class ProfileCard(Component):
|
|||
|
||||
```py
|
||||
class ProfileCard(Component):
|
||||
class Args(NamedTuple):
|
||||
class Args:
|
||||
user_id: int
|
||||
|
||||
class Kwargs(NamedTuple):
|
||||
class Kwargs:
|
||||
show_details: bool
|
||||
|
||||
# Access inputs directly as parameters
|
||||
|
|
@ -228,10 +228,10 @@ class ProfileCard(Component):
|
|||
|
||||
```py
|
||||
class ProfileCard(Component):
|
||||
class Args(NamedTuple):
|
||||
class Args:
|
||||
user_id: int
|
||||
|
||||
class Kwargs(NamedTuple):
|
||||
class Kwargs:
|
||||
show_details: bool
|
||||
|
||||
def get_template_data(self, args: Args, kwargs: Kwargs, slots, context):
|
||||
|
|
@ -320,7 +320,7 @@ from django_components import Component, Default, register
|
|||
|
||||
@register("profile_card")
|
||||
class ProfileCard(Component):
|
||||
class Kwargs(NamedTuple):
|
||||
class Kwargs:
|
||||
show_details: bool
|
||||
|
||||
class Defaults:
|
||||
|
|
@ -344,7 +344,7 @@ class ProfileCard(Component):
|
|||
|
||||
```py
|
||||
class ProfileCard(Component):
|
||||
class Kwargs(NamedTuple):
|
||||
class Kwargs:
|
||||
show_details: bool = True
|
||||
```
|
||||
|
||||
|
|
@ -391,18 +391,18 @@ This will also validate the inputs at runtime, as the type classes will be insta
|
|||
Read more about [Component typing](../../fundamentals/typing_and_validation).
|
||||
|
||||
```python
|
||||
from typing import NamedTuple, Optional
|
||||
from typing import Optional
|
||||
from django_components import Component, SlotInput
|
||||
|
||||
class Button(Component):
|
||||
class Args(NamedTuple):
|
||||
class Args:
|
||||
name: str
|
||||
|
||||
class Kwargs(NamedTuple):
|
||||
class Kwargs:
|
||||
surname: str
|
||||
maybe_var: Optional[int] = None # May be omitted
|
||||
|
||||
class Slots(NamedTuple):
|
||||
class Slots:
|
||||
my_slot: Optional[SlotInput] = None
|
||||
footer: SlotInput
|
||||
|
||||
|
|
@ -433,19 +433,18 @@ and [`CssData`](../../../reference/api/#django_components.Component.CssData) cla
|
|||
For each data method, you can either return a plain dictionary with the data, or an instance of the respective data class.
|
||||
|
||||
```python
|
||||
from typing import NamedTuple
|
||||
from django_components import Component
|
||||
|
||||
class Button(Component):
|
||||
class TemplateData(NamedTuple):
|
||||
class TemplateData(
|
||||
data1: str
|
||||
data2: int
|
||||
|
||||
class JsData(NamedTuple):
|
||||
class JsData:
|
||||
js_data1: str
|
||||
js_data2: int
|
||||
|
||||
class CssData(NamedTuple):
|
||||
class CssData:
|
||||
css_data1: str
|
||||
css_data2: int
|
||||
|
||||
|
|
|
|||
|
|
@ -92,7 +92,7 @@ With `Args` class:
|
|||
from django_components import Component
|
||||
|
||||
class Table(Component):
|
||||
class Args(NamedTuple):
|
||||
class Args:
|
||||
page: int
|
||||
per_page: int
|
||||
|
||||
|
|
@ -137,7 +137,7 @@ With `Kwargs` class:
|
|||
from django_components import Component
|
||||
|
||||
class Table(Component):
|
||||
class Kwargs(NamedTuple):
|
||||
class Kwargs:
|
||||
page: int
|
||||
per_page: int
|
||||
|
||||
|
|
@ -182,7 +182,7 @@ With `Slots` class:
|
|||
from django_components import Component, Slot, SlotInput
|
||||
|
||||
class Table(Component):
|
||||
class Slots(NamedTuple):
|
||||
class Slots:
|
||||
header: SlotInput
|
||||
footer: SlotInput
|
||||
|
||||
|
|
|
|||
|
|
@ -80,14 +80,13 @@ For a component to be renderable with the [`{% component %}`](../../../reference
|
|||
For example, if you register a component under the name `"button"`:
|
||||
|
||||
```python
|
||||
from typing import NamedTuple
|
||||
from django_components import Component, register
|
||||
|
||||
@register("button")
|
||||
class Button(Component):
|
||||
template_file = "button.html"
|
||||
|
||||
class Kwargs(NamedTuple):
|
||||
class Kwargs:
|
||||
name: str
|
||||
job: str
|
||||
|
||||
|
|
@ -207,20 +206,20 @@ The [`Component.render()`](../../../reference/api/#django_components.Component.r
|
|||
This is the equivalent of calling the [`{% component %}`](../template_tags#component) tag.
|
||||
|
||||
```python
|
||||
from typing import NamedTuple, Optional
|
||||
from typing import Optional
|
||||
from django_components import Component, SlotInput
|
||||
|
||||
class Button(Component):
|
||||
template_file = "button.html"
|
||||
|
||||
class Args(NamedTuple):
|
||||
class Args:
|
||||
name: str
|
||||
|
||||
class Kwargs(NamedTuple):
|
||||
class Kwargs:
|
||||
surname: str
|
||||
age: int
|
||||
|
||||
class Slots(NamedTuple):
|
||||
class Slots:
|
||||
footer: Optional[SlotInput] = None
|
||||
|
||||
def get_template_data(self, args, kwargs, slots, context):
|
||||
|
|
@ -264,20 +263,20 @@ Any extra arguments are passed to the [`HttpResponse`](https://docs.djangoprojec
|
|||
constructor.
|
||||
|
||||
```python
|
||||
from typing import NamedTuple, Optional
|
||||
from typing import Optional
|
||||
from django_components import Component, SlotInput
|
||||
|
||||
class Button(Component):
|
||||
template_file = "button.html"
|
||||
|
||||
class Args(NamedTuple):
|
||||
class Args(
|
||||
name: str
|
||||
|
||||
class Kwargs(NamedTuple):
|
||||
class Kwargs:
|
||||
surname: str
|
||||
age: int
|
||||
|
||||
class Slots(NamedTuple):
|
||||
class Slots:
|
||||
footer: Optional[SlotInput] = None
|
||||
|
||||
def get_template_data(self, args, kwargs, slots, context):
|
||||
|
|
@ -486,19 +485,19 @@ and [`Slots`](../../../reference/api/#django_components.Component.Slots) classes
|
|||
Read more on [Typing and validation](../../fundamentals/typing_and_validation).
|
||||
|
||||
```python
|
||||
from typing import NamedTuple, Optional
|
||||
from typing import Optional
|
||||
from django_components import Component, Slot, SlotInput
|
||||
|
||||
# Define the component with the types
|
||||
class Button(Component):
|
||||
class Args(NamedTuple):
|
||||
class Args(
|
||||
name: str
|
||||
|
||||
class Kwargs(NamedTuple):
|
||||
class Kwargs:
|
||||
surname: str
|
||||
age: int
|
||||
|
||||
class Slots(NamedTuple):
|
||||
class Slots:
|
||||
my_slot: Optional[SlotInput] = None
|
||||
footer: SlotInput
|
||||
|
||||
|
|
|
|||
|
|
@ -20,20 +20,20 @@ that allow you to define the types of args, kwargs, slots, as well as the data r
|
|||
Use this to add type hints to your components, to validate the inputs at runtime, and to document them.
|
||||
|
||||
```py
|
||||
from typing import NamedTuple, Optional
|
||||
from typing import Optional
|
||||
from django.template import Context
|
||||
from django_components import Component, SlotInput
|
||||
|
||||
class Button(Component):
|
||||
class Args(NamedTuple):
|
||||
class Args:
|
||||
size: int
|
||||
text: str
|
||||
|
||||
class Kwargs(NamedTuple):
|
||||
class Kwargs:
|
||||
variable: str
|
||||
maybe_var: Optional[int] = None # May be omitted
|
||||
|
||||
class Slots(NamedTuple):
|
||||
class Slots:
|
||||
my_slot: Optional[SlotInput] = None
|
||||
|
||||
def get_template_data(self, args: Args, kwargs: Kwargs, slots: Slots, context: Context):
|
||||
|
|
@ -59,7 +59,7 @@ You can use [`Component.Args`](../../../reference/api#django_components.Componen
|
|||
[`Component.Kwargs`](../../../reference/api#django_components.Component.Kwargs),
|
||||
and [`Component.Slots`](../../../reference/api#django_components.Component.Slots) to type the component inputs.
|
||||
|
||||
When you set these classes, at render time the `args`, `kwargs`, and `slots` parameters of the data methods
|
||||
When you set these input classes, the `args`, `kwargs`, and `slots` parameters of the data methods
|
||||
([`get_template_data()`](../../../reference/api#django_components.Component.get_template_data),
|
||||
[`get_js_data()`](../../../reference/api#django_components.Component.get_js_data),
|
||||
[`get_css_data()`](../../../reference/api#django_components.Component.get_css_data))
|
||||
|
|
@ -80,7 +80,7 @@ or set them to `None`, the inputs will be passed as plain lists or dictionaries,
|
|||
and will not be validated.
|
||||
|
||||
```python
|
||||
from typing_extensions import NamedTuple, TypedDict
|
||||
from typing_extensions import TypedDict
|
||||
from django.template import Context
|
||||
from django_components import Component, Slot, SlotInput
|
||||
|
||||
|
|
@ -90,15 +90,15 @@ class ButtonFooterSlotData(TypedDict):
|
|||
|
||||
# Define the component with the types
|
||||
class Button(Component):
|
||||
class Args(NamedTuple):
|
||||
class Args:
|
||||
name: str
|
||||
|
||||
class Kwargs(NamedTuple):
|
||||
class Kwargs:
|
||||
surname: str
|
||||
age: int
|
||||
maybe_var: Optional[int] = None # May be omitted
|
||||
|
||||
class Slots(NamedTuple):
|
||||
class Slots:
|
||||
# Use `SlotInput` to allow slots to be given as `Slot` instance,
|
||||
# plain string, or a function that returns a string.
|
||||
my_slot: Optional[SlotInput] = None
|
||||
|
|
@ -143,7 +143,7 @@ class Button(Component):
|
|||
Args = None
|
||||
Slots = None
|
||||
|
||||
class Kwargs(NamedTuple):
|
||||
class Kwargs:
|
||||
name: str
|
||||
age: int
|
||||
|
||||
|
|
@ -195,19 +195,18 @@ If you omit the [`TemplateData`](../../../reference/api#django_components.Compon
|
|||
or set them to `None`, the validation and instantiation will be skipped.
|
||||
|
||||
```python
|
||||
from typing import NamedTuple
|
||||
from django_components import Component
|
||||
|
||||
class Button(Component):
|
||||
class TemplateData(NamedTuple):
|
||||
class TemplateData:
|
||||
data1: str
|
||||
data2: int
|
||||
|
||||
class JsData(NamedTuple):
|
||||
class JsData:
|
||||
js_data1: str
|
||||
js_data2: int
|
||||
|
||||
class CssData(NamedTuple):
|
||||
class CssData:
|
||||
css_data1: str
|
||||
css_data2: int
|
||||
|
||||
|
|
@ -233,19 +232,18 @@ class Button(Component):
|
|||
For each data method, you can either return a plain dictionary with the data, or an instance of the respective data class directly.
|
||||
|
||||
```python
|
||||
from typing import NamedTuple
|
||||
from django_components import Component
|
||||
|
||||
class Button(Component):
|
||||
class TemplateData(NamedTuple):
|
||||
class TemplateData:
|
||||
data1: str
|
||||
data2: int
|
||||
|
||||
class JsData(NamedTuple):
|
||||
class JsData:
|
||||
js_data1: str
|
||||
js_data2: int
|
||||
|
||||
class CssData(NamedTuple):
|
||||
class CssData:
|
||||
css_data1: str
|
||||
css_data2: int
|
||||
|
||||
|
|
@ -270,34 +268,63 @@ class Button(Component):
|
|||
|
||||
## Custom types
|
||||
|
||||
We recommend to use [`NamedTuple`](https://docs.python.org/3/library/typing.html#typing.NamedTuple)
|
||||
for the `Args` class, and [`NamedTuple`](https://docs.python.org/3/library/typing.html#typing.NamedTuple),
|
||||
So far, we've defined the input classes like `Kwargs` as simple classes.
|
||||
|
||||
The truth is that when these classes don't subclass anything else,
|
||||
they are converted to `NamedTuples` behind the scenes.
|
||||
|
||||
```py
|
||||
class Table(Component):
|
||||
class Kwargs:
|
||||
name: str
|
||||
age: int
|
||||
```
|
||||
|
||||
is the same as:
|
||||
|
||||
```py
|
||||
class Table(Component):
|
||||
class Kwargs(NamedTuple):
|
||||
name: str
|
||||
age: int
|
||||
```
|
||||
|
||||
You can actually set these classes to anything you want - whether it's dataclasses,
|
||||
[Pydantic models](https://docs.pydantic.dev/latest/concepts/models/), or custom classes:
|
||||
|
||||
```py
|
||||
from typing import NamedTuple, Optional
|
||||
from django_components import Component, Optional
|
||||
from pydantic import BaseModel
|
||||
|
||||
class Button(Component):
|
||||
class Args(NamedTuple):
|
||||
size: int
|
||||
text: str
|
||||
|
||||
@dataclass
|
||||
class Kwargs:
|
||||
variable: str
|
||||
maybe_var: Optional[int] = None
|
||||
|
||||
class Slots(BaseModel):
|
||||
my_slot: Optional[SlotInput] = None
|
||||
|
||||
def get_template_data(self, args: Args, kwargs: Kwargs, slots: Slots, context: Context):
|
||||
...
|
||||
```
|
||||
|
||||
We recommend:
|
||||
|
||||
- [`NamedTuple`](https://docs.python.org/3/library/typing.html#typing.NamedTuple)
|
||||
for the `Args` class
|
||||
- [`NamedTuple`](https://docs.python.org/3/library/typing.html#typing.NamedTuple),
|
||||
[dataclasses](https://docs.python.org/3/library/dataclasses.html#dataclasses.dataclass),
|
||||
or [Pydantic models](https://docs.pydantic.dev/latest/concepts/models/)
|
||||
for `Kwargs`, `Slots`, `TemplateData`, `JsData`, and `CssData` classes.
|
||||
|
||||
However, you can use any class, as long as they meet the conditions below.
|
||||
|
||||
For example, here is how you can use [Pydantic models](https://docs.pydantic.dev/latest/concepts/models/)
|
||||
to validate the inputs at runtime.
|
||||
|
||||
```py
|
||||
from django_components import Component
|
||||
from pydantic import BaseModel
|
||||
|
||||
class Table(Component):
|
||||
class Kwargs(BaseModel):
|
||||
name: str
|
||||
age: int
|
||||
|
||||
def get_template_data(self, args, kwargs, slots, context):
|
||||
assert isinstance(kwargs, Table.Kwargs)
|
||||
|
||||
Table.render(
|
||||
kwargs=Table.Kwargs(name="John", age=30),
|
||||
)
|
||||
```
|
||||
|
||||
### `Args` class
|
||||
|
||||
The [`Args`](../../../reference/api#django_components.Component.Args) class
|
||||
|
|
@ -390,7 +417,7 @@ As a workaround:
|
|||
|
||||
```py
|
||||
class Table(Component):
|
||||
class Args(NamedTuple):
|
||||
class Args:
|
||||
args: List[str]
|
||||
|
||||
Table.render(
|
||||
|
|
@ -402,7 +429,7 @@ As a workaround:
|
|||
|
||||
```py
|
||||
class Table(Component):
|
||||
class Kwargs(NamedTuple):
|
||||
class Kwargs:
|
||||
variable: str
|
||||
another: int
|
||||
# Pass any extra keys under `extra`
|
||||
|
|
@ -422,17 +449,16 @@ As a workaround:
|
|||
To declare that a component accepts no args, kwargs, etc, define the types with no attributes using the `pass` keyword:
|
||||
|
||||
```py
|
||||
from typing import NamedTuple
|
||||
from django_components import Component
|
||||
|
||||
class Button(Component):
|
||||
class Args(NamedTuple):
|
||||
class Args:
|
||||
pass
|
||||
|
||||
class Kwargs(NamedTuple):
|
||||
class Kwargs:
|
||||
pass
|
||||
|
||||
class Slots(NamedTuple):
|
||||
class Slots:
|
||||
pass
|
||||
```
|
||||
|
||||
|
|
@ -459,14 +485,14 @@ In the example below, `ButtonExtra` inherits `Kwargs` from `Button`, but overrid
|
|||
from django_components import Component, Empty
|
||||
|
||||
class Button(Component):
|
||||
class Args(NamedTuple):
|
||||
class Args:
|
||||
size: int
|
||||
|
||||
class Kwargs(NamedTuple):
|
||||
class Kwargs:
|
||||
color: str
|
||||
|
||||
class ButtonExtra(Button):
|
||||
class Args(NamedTuple):
|
||||
class Args:
|
||||
name: str
|
||||
size: int
|
||||
|
||||
|
|
@ -491,10 +517,10 @@ Compare the following:
|
|||
|
||||
```py
|
||||
class Button(Component):
|
||||
class Args(NamedTuple):
|
||||
class Args:
|
||||
size: int
|
||||
|
||||
class Kwargs(NamedTuple):
|
||||
class Kwargs:
|
||||
color: str
|
||||
|
||||
# Both `Args` and `Kwargs` are defined on the class
|
||||
|
|
@ -624,23 +650,23 @@ The steps to migrate are:
|
|||
Thus, the code above will become:
|
||||
|
||||
```py
|
||||
from typing import NamedTuple, Optional
|
||||
from typing import Optional
|
||||
from django.template import Context
|
||||
from django_components import Component, Slot, SlotInput
|
||||
|
||||
# The Component class does not take any generics
|
||||
class Button(Component):
|
||||
# Types are now defined inside the component class
|
||||
class Args(NamedTuple):
|
||||
class Args:
|
||||
size: int
|
||||
text: str
|
||||
|
||||
class Kwargs(NamedTuple):
|
||||
class Kwargs:
|
||||
variable: str
|
||||
another: int
|
||||
maybe_var: Optional[int] = None # May be omitted
|
||||
|
||||
class Slots(NamedTuple):
|
||||
class Slots:
|
||||
# Use `SlotInput` to allow slots to be given as `Slot` instance,
|
||||
# plain string, or a function that returns a string.
|
||||
my_slot: Optional[SlotInput] = None
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue