fastlifeweb 0.20.1__py3-none-any.whl → 0.21.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- CHANGELOG.md +10 -0
- fastlife/__init__.py +5 -0
- fastlife/adapters/jinjax/renderer.py +4 -52
- fastlife/adapters/jinjax/widget_factory/bool_builder.py +2 -2
- fastlife/adapters/jinjax/widget_factory/emailstr_builder.py +5 -4
- fastlife/adapters/jinjax/widget_factory/enum_builder.py +2 -2
- fastlife/adapters/jinjax/widget_factory/factory.py +20 -21
- fastlife/adapters/jinjax/widget_factory/literal_builder.py +7 -6
- fastlife/adapters/jinjax/widget_factory/model_builder.py +3 -3
- fastlife/adapters/jinjax/widget_factory/secretstr_builder.py +2 -2
- fastlife/adapters/jinjax/widget_factory/sequence_builder.py +3 -3
- fastlife/adapters/jinjax/widget_factory/set_builder.py +2 -2
- fastlife/adapters/jinjax/widget_factory/simpletype_builder.py +7 -8
- fastlife/adapters/jinjax/widget_factory/union_builder.py +3 -3
- fastlife/adapters/jinjax/widgets/base.py +35 -35
- fastlife/adapters/jinjax/widgets/boolean.py +13 -34
- fastlife/adapters/jinjax/widgets/checklist.py +36 -42
- fastlife/adapters/jinjax/widgets/dropdown.py +32 -38
- fastlife/adapters/jinjax/widgets/hidden.py +7 -15
- fastlife/adapters/jinjax/widgets/model.py +33 -40
- fastlife/adapters/jinjax/widgets/sequence.py +61 -40
- fastlife/adapters/jinjax/widgets/text.py +39 -78
- fastlife/adapters/jinjax/widgets/union.py +50 -57
- fastlife/components/CsrfToken.jinja +1 -1
- fastlife/components/pydantic_form/Widget.jinja +4 -3
- fastlife/config/configurator.py +65 -19
- fastlife/config/exceptions.py +0 -2
- fastlife/config/views.py +0 -2
- fastlife/domain/__init__.py +1 -0
- fastlife/domain/model/__init__.py +1 -0
- fastlife/domain/model/security.py +19 -0
- fastlife/domain/model/template.py +30 -0
- fastlife/domain/model/types.py +17 -0
- fastlife/request/request.py +19 -0
- fastlife/security/csrf.py +3 -13
- fastlife/services/templates.py +9 -42
- fastlife/services/translations.py +12 -0
- fastlife/templates/__init__.py +1 -6
- fastlife/templates/inline.py +18 -14
- {fastlifeweb-0.20.1.dist-info → fastlifeweb-0.21.0.dist-info}/METADATA +1 -1
- {fastlifeweb-0.20.1.dist-info → fastlifeweb-0.21.0.dist-info}/RECORD +44 -49
- fastlife/components/pydantic_form/Boolean.jinja +0 -13
- fastlife/components/pydantic_form/Checklist.jinja +0 -21
- fastlife/components/pydantic_form/Dropdown.jinja +0 -18
- fastlife/components/pydantic_form/Hidden.jinja +0 -3
- fastlife/components/pydantic_form/Model.jinja +0 -30
- fastlife/components/pydantic_form/Sequence.jinja +0 -47
- fastlife/components/pydantic_form/Text.jinja +0 -11
- fastlife/components/pydantic_form/Textarea.jinja +0 -38
- fastlife/components/pydantic_form/Union.jinja +0 -34
- fastlife/templates/binding.py +0 -52
- {fastlifeweb-0.20.1.dist-info → fastlifeweb-0.21.0.dist-info}/WHEEL +0 -0
- {fastlifeweb-0.20.1.dist-info → fastlifeweb-0.21.0.dist-info}/entry_points.txt +0 -0
- {fastlifeweb-0.20.1.dist-info → fastlifeweb-0.21.0.dist-info}/licenses/LICENSE +0 -0
@@ -3,8 +3,9 @@ Widget for field of type Set.
|
|
3
3
|
"""
|
4
4
|
|
5
5
|
from collections.abc import Sequence
|
6
|
+
from typing import Self
|
6
7
|
|
7
|
-
from pydantic import BaseModel, Field
|
8
|
+
from pydantic import BaseModel, Field, model_validator
|
8
9
|
|
9
10
|
from .base import Widget
|
10
11
|
|
@@ -19,52 +20,45 @@ class Checkable(BaseModel):
|
|
19
20
|
checked: bool
|
20
21
|
error: str | None = Field(default=None)
|
21
22
|
|
22
|
-
|
23
|
-
|
24
|
-
id = f"{self.name}-{self.value}-{self.token}"
|
25
|
-
return id.replace(".", "-").replace("_", "-")
|
23
|
+
id: str | None = Field(default=None)
|
24
|
+
field_name: str | None = Field(default=None)
|
26
25
|
|
27
|
-
@
|
28
|
-
def
|
29
|
-
|
26
|
+
@model_validator(mode="after")
|
27
|
+
def fill_props(self) -> Self:
|
28
|
+
self.id = f"{self.name}-{self.value}-{self.token}".replace(".", "-")
|
29
|
+
self.field_name = f"{self.name}[]"
|
30
|
+
return self
|
30
31
|
|
31
32
|
|
32
33
|
class ChecklistWidget(Widget[Sequence[Checkable]]):
|
33
34
|
"""
|
34
35
|
Widget for field of type Set.
|
35
|
-
|
36
|
-
:param name: field name.
|
37
|
-
:param title: title for the widget.
|
38
|
-
:param hint: hint for human.
|
39
|
-
:param aria_label: html input aria-label value.
|
40
|
-
:param value: current value.
|
41
|
-
:param error: error of the value if any.
|
42
|
-
:param removable: display a button to remove the widget for optional fields.
|
43
|
-
:param token: token used to get unique id on the form.
|
44
36
|
"""
|
45
37
|
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
38
|
+
template = """
|
39
|
+
<pydantic_form.Widget :widget_id="id" :removable="removable">
|
40
|
+
<div class="pt-4">
|
41
|
+
<Details>
|
42
|
+
<Summary :id="id + '-summary'">
|
43
|
+
<H3 :class="H3_SUMMARY_CLASS">{{title}}</H3>
|
44
|
+
<pydantic_form.Error :text="error" />
|
45
|
+
</Summary>
|
46
|
+
<div>
|
47
|
+
{% for value in value %}
|
48
|
+
<div class="flex items-center mb-4">
|
49
|
+
<Checkbox :name="value.field_name" type="checkbox"
|
50
|
+
:id="value.id"
|
51
|
+
:value="value.value"
|
52
|
+
:checked="value.checked" />
|
53
|
+
<Label :for="value.id"
|
54
|
+
class="ms-2 text-base text-neutral-900 dark:text-white">
|
55
|
+
{{- value.label -}}
|
56
|
+
</Label>
|
57
|
+
<pydantic_form.Error :text="value.error" />
|
58
|
+
</div>
|
59
|
+
{% endfor %}
|
60
|
+
</div>
|
61
|
+
</Details>
|
62
|
+
</div>
|
63
|
+
</pydantic_form.Widget>
|
64
|
+
"""
|
@@ -4,53 +4,47 @@ Widget for field of type Enum or Literal.
|
|
4
4
|
|
5
5
|
from collections.abc import Sequence
|
6
6
|
|
7
|
+
from pydantic import Field, field_validator
|
8
|
+
|
7
9
|
from .base import Widget
|
8
10
|
|
9
11
|
|
10
12
|
class DropDownWidget(Widget[str]):
|
11
13
|
"""
|
12
14
|
Widget for field of type Enum or Literal.
|
15
|
+
"""
|
13
16
|
|
14
|
-
|
15
|
-
:
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
17
|
+
template = """
|
18
|
+
<pydantic_form.Widget :widget_id="id" :removable="removable">
|
19
|
+
<div class="pt-4">
|
20
|
+
<Label :for="id">{{title}}</Label>
|
21
|
+
<Select :name="name" :id="id">
|
22
|
+
{%- for opt in options -%}
|
23
|
+
<Option :value="opt.value" id={{id + "-" + opt.value.replace(" ", " -")}}
|
24
|
+
:selected="value==opt.value">
|
25
|
+
{{- opt.text -}}
|
26
|
+
</Option>
|
27
|
+
{%- endfor -%}
|
28
|
+
</Select>
|
29
|
+
<pydantic_form.Error :text="error" />
|
30
|
+
<pydantic_form.Hint :text="hint" />
|
31
|
+
</div>
|
32
|
+
</pydantic_form.Widget>
|
23
33
|
"""
|
24
34
|
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
removable: bool = False,
|
36
|
-
token: str | None = None,
|
37
|
-
) -> None:
|
38
|
-
super().__init__(
|
39
|
-
name,
|
40
|
-
value=value,
|
41
|
-
error=error,
|
42
|
-
title=title,
|
43
|
-
token=token,
|
44
|
-
removable=removable,
|
45
|
-
hint=hint,
|
46
|
-
aria_label=aria_label,
|
47
|
-
)
|
48
|
-
self.options: list[dict[str, str]] = []
|
35
|
+
options: list[dict[str | int, str | int]] = Field(default_factory=list)
|
36
|
+
|
37
|
+
@field_validator("options", mode="before")
|
38
|
+
@classmethod
|
39
|
+
def validate_options(
|
40
|
+
cls, options: Sequence[str | int | tuple[str | int, str | int]] | None
|
41
|
+
) -> Sequence[dict[str | int, str | int]]:
|
42
|
+
if not options:
|
43
|
+
return []
|
44
|
+
ret: list[dict[str | int, str | int]] = []
|
49
45
|
for opt in options:
|
50
46
|
if isinstance(opt, tuple):
|
51
|
-
|
47
|
+
ret.append({"value": opt[0], "text": opt[1]})
|
52
48
|
else:
|
53
|
-
|
54
|
-
|
55
|
-
def get_template(self) -> str:
|
56
|
-
return "pydantic_form.Dropdown.jinja"
|
49
|
+
ret.append({"value": opt, "text": opt})
|
50
|
+
return ret
|
@@ -1,33 +1,25 @@
|
|
1
1
|
"""Hidden fields"""
|
2
2
|
|
3
|
-
from
|
3
|
+
from fastlife.domain.model.types import Builtins
|
4
4
|
|
5
5
|
from .base import Widget
|
6
6
|
|
7
7
|
|
8
|
-
class HiddenWidget(Widget[
|
8
|
+
class HiddenWidget(Widget[Builtins]):
|
9
9
|
'''
|
10
10
|
Widget to annotate to display a field as an hidden field.
|
11
11
|
|
12
12
|
::
|
13
13
|
from pydantic import BaseModel
|
14
|
+
from fastlife.adapters.jinjax.widgets.base import CustomWidget
|
14
15
|
from fastlife.adapters.jinjax.widgets.hidden import HiddenWidget
|
15
16
|
|
16
17
|
class MyForm(BaseModel):
|
17
|
-
id: Annotated[str, HiddenWidget] = Field(...)
|
18
|
+
id: Annotated[str, CustomWidget(HiddenWidget)] = Field(...)
|
18
19
|
"""Identifier in the database."""
|
19
20
|
|
20
21
|
'''
|
21
22
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
*,
|
26
|
-
value: str,
|
27
|
-
token: str,
|
28
|
-
**kwargs: Any,
|
29
|
-
) -> None:
|
30
|
-
super().__init__(name, value=value, token=token)
|
31
|
-
|
32
|
-
def get_template(self) -> str:
|
33
|
-
return "pydantic_form.Hidden.jinja"
|
23
|
+
template = """
|
24
|
+
<Hidden :name="name" :value="value" :id="id" />
|
25
|
+
"""
|
@@ -1,51 +1,44 @@
|
|
1
1
|
"""Pydantic models"""
|
2
2
|
|
3
3
|
from collections.abc import Sequence
|
4
|
-
from typing import Any
|
5
4
|
|
6
5
|
from markupsafe import Markup
|
6
|
+
from pydantic import Field
|
7
7
|
|
8
8
|
from fastlife.services.templates import AbstractTemplateRenderer
|
9
9
|
|
10
|
-
from .base import Widget
|
11
|
-
|
12
|
-
|
13
|
-
class ModelWidget(Widget[Sequence[
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
return "pydantic_form.Model.jinja"
|
10
|
+
from .base import TWidget, Widget
|
11
|
+
|
12
|
+
|
13
|
+
class ModelWidget(Widget[Sequence[TWidget]]):
|
14
|
+
template = """
|
15
|
+
<pydantic_form.Widget :widget_id="id" :removable="removable">
|
16
|
+
<div id="{{id}}"{% if nested %} class="m-4"{%endif%}>
|
17
|
+
{% if nested %}
|
18
|
+
<Details>
|
19
|
+
<Summary :id="id + '-summary'">
|
20
|
+
<H3 :class="H3_SUMMARY_CLASS">{{title}}</H3>
|
21
|
+
<pydantic_form.Error :text="error" />
|
22
|
+
</Summary>
|
23
|
+
<div>
|
24
|
+
{% for child in children_widgets %}
|
25
|
+
{{ child }}
|
26
|
+
{% endfor %}
|
27
|
+
</div>
|
28
|
+
</Details>
|
29
|
+
{% else %}
|
30
|
+
{% for child in children_widgets %}
|
31
|
+
{{ child }}
|
32
|
+
{% endfor %}
|
33
|
+
{% endif %}
|
34
|
+
</div>
|
35
|
+
</pydantic_form.Widget>
|
36
|
+
"""
|
37
|
+
|
38
|
+
nested: bool = Field(default=False)
|
39
|
+
children_widgets: list[str] | None = Field(default=None)
|
41
40
|
|
42
41
|
def to_html(self, renderer: AbstractTemplateRenderer) -> Markup:
|
43
42
|
"""Return the html version."""
|
44
|
-
|
45
|
-
|
46
|
-
"widget": self,
|
47
|
-
"children_widget": children_widget,
|
48
|
-
}
|
49
|
-
return Markup(
|
50
|
-
renderer.render_template(self.get_template(), globals=None, **kwargs)
|
51
|
-
)
|
43
|
+
self.children_widgets = [child.to_html(renderer) for child in self.value or []]
|
44
|
+
return Markup(renderer.render_template(self))
|
@@ -2,52 +2,73 @@ from collections.abc import Sequence
|
|
2
2
|
from typing import Any
|
3
3
|
|
4
4
|
from markupsafe import Markup
|
5
|
+
from pydantic import Field
|
5
6
|
|
6
7
|
from fastlife.services.templates import AbstractTemplateRenderer
|
7
8
|
|
8
|
-
from .base import TypeWrapper, Widget
|
9
|
-
|
10
|
-
|
11
|
-
class SequenceWidget(Widget[Sequence[
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
9
|
+
from .base import TWidget, TypeWrapper, Widget
|
10
|
+
|
11
|
+
|
12
|
+
class SequenceWidget(Widget[Sequence[TWidget]]):
|
13
|
+
template = """
|
14
|
+
<pydantic_form.Widget :widget_id="id" :removable="removable">
|
15
|
+
<Details :id="id">
|
16
|
+
<Summary :id="id + '-summary'">
|
17
|
+
<H3 :class="H3_SUMMARY_CLASS">{{title}}</H3>
|
18
|
+
<pydantic_form.Error :text="error" />
|
19
|
+
</Summary>
|
20
|
+
<div>
|
21
|
+
{% set fnGetName = "get" + id.replace("-", "_") %}
|
22
|
+
<script>
|
23
|
+
function {{ fnGetName }} () {
|
24
|
+
const el = document.getElementById("{{id}}-content");
|
25
|
+
const len = el.dataset.length;
|
26
|
+
el.dataset.length = parseInt(len) + 1;
|
27
|
+
return "{{wrapped_type.name}}." + len;
|
28
|
+
}
|
29
|
+
</script>
|
30
|
+
|
31
|
+
<div id="{{id}}-content" class="m-4"
|
32
|
+
data-length="{{children_widgets|length|string}}">
|
33
|
+
{% set container_id = id + "-children-container" %}
|
34
|
+
<div id="{{container_id}}">
|
35
|
+
{% for child in children_widgets %}
|
36
|
+
{{ child }}
|
37
|
+
{% endfor%}
|
38
|
+
</div>
|
39
|
+
</div>
|
40
|
+
|
41
|
+
<div>
|
42
|
+
{% set container_id = "#" + id + "-children-container" %}
|
43
|
+
{% set add_id = id + "-add" %}
|
44
|
+
{% set vals = 'js:{"name": '
|
45
|
+
+ fnGetName
|
46
|
+
+ '(), "token": "'
|
47
|
+
+ wrapped_type.token + '", "removable": true}' %}
|
48
|
+
<Button type="button"
|
49
|
+
:hx-target="container_id" hx-swap="beforeend"
|
50
|
+
:id="add_id"
|
51
|
+
:hx-vals="vals"
|
52
|
+
:hx-get="wrapped_type.url">
|
53
|
+
Add
|
54
|
+
</Button>
|
55
|
+
</div>
|
56
|
+
</div>
|
57
|
+
</Details>
|
58
|
+
</pydantic_form.Widget>
|
59
|
+
"""
|
60
|
+
|
61
|
+
item_type: type[Any]
|
62
|
+
wrapped_type: TypeWrapper | None = Field(default=None)
|
63
|
+
children_widgets: list[str] = Field(default=None)
|
39
64
|
|
40
65
|
def build_item_type(self, route_prefix: str) -> TypeWrapper:
|
41
66
|
return TypeWrapper(self.item_type, route_prefix, self.name, self.token)
|
42
67
|
|
43
68
|
def to_html(self, renderer: "AbstractTemplateRenderer") -> Markup:
|
44
69
|
"""Return the html version."""
|
45
|
-
|
46
|
-
|
47
|
-
renderer.
|
48
|
-
|
49
|
-
|
50
|
-
type=self.build_item_type(renderer.route_prefix),
|
51
|
-
children_widgets=children,
|
52
|
-
)
|
53
|
-
)
|
70
|
+
self.wrapped_type = self.build_item_type(renderer.route_prefix)
|
71
|
+
self.children_widgets = [
|
72
|
+
Markup(item.to_html(renderer)) for item in self.value or []
|
73
|
+
]
|
74
|
+
return Markup(renderer.render_template(self))
|
@@ -1,52 +1,31 @@
|
|
1
1
|
from collections.abc import Sequence
|
2
2
|
|
3
|
+
from pydantic import Field
|
4
|
+
|
5
|
+
from fastlife.domain.model.types import Builtins
|
6
|
+
|
3
7
|
from .base import Widget
|
4
8
|
|
5
9
|
|
6
|
-
class TextWidget(Widget[
|
10
|
+
class TextWidget(Widget[Builtins]):
|
7
11
|
"""
|
8
12
|
Widget for text like field (email, ...).
|
9
|
-
|
10
|
-
:param name: input name.
|
11
|
-
:param title: title for the widget.
|
12
|
-
:param hint: hint for human.
|
13
|
-
:param aria_label: html input aria-label value.
|
14
|
-
:param placeholder: html input placeholder value.
|
15
|
-
:param error: error of the value if any.
|
16
|
-
:param value: current value.
|
17
|
-
:param removable: display a button to remove the widget for optional fields.
|
18
|
-
:param token: token used to get unique id on the form.
|
19
13
|
"""
|
20
14
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
removable: bool = False,
|
33
|
-
token: str,
|
34
|
-
) -> None:
|
35
|
-
super().__init__(
|
36
|
-
name,
|
37
|
-
value=value,
|
38
|
-
title=title,
|
39
|
-
hint=hint,
|
40
|
-
aria_label=aria_label,
|
41
|
-
token=token,
|
42
|
-
error=error,
|
43
|
-
removable=removable,
|
44
|
-
)
|
45
|
-
self.placeholder = placeholder or ""
|
46
|
-
self.input_type = input_type
|
15
|
+
template = """
|
16
|
+
<pydantic_form.Widget :widget_id="id" :removable="removable">
|
17
|
+
<div class="pt-4">
|
18
|
+
<Label :for="id">{{title}}</Label>
|
19
|
+
<pydantic_form.Error :text="error" />
|
20
|
+
<Input :name="name" :value="value" :type="input_type" :id="id"
|
21
|
+
:aria-label="aria_label" :placeholder="placeholder" />
|
22
|
+
<pydantic_form.Hint :text="hint" />
|
23
|
+
</div>
|
24
|
+
</pydantic_form.Widget>
|
25
|
+
"""
|
47
26
|
|
48
|
-
|
49
|
-
|
27
|
+
input_type: str = Field(default="text")
|
28
|
+
placeholder: str | None = Field(default=None)
|
50
29
|
|
51
30
|
|
52
31
|
class TextareaWidget(Widget[Sequence[str]]):
|
@@ -54,12 +33,13 @@ class TextareaWidget(Widget[Sequence[str]]):
|
|
54
33
|
Render a Textearea for a string or event a sequence of string.
|
55
34
|
|
56
35
|
```
|
36
|
+
from fastlife.adapters.jinjax.widgets.base import CustomWidget
|
57
37
|
from fastlife.adapters.jinjax.widgets.text import TextareaWidget
|
58
38
|
from pydantic import BaseModel, Field, field_validator
|
59
39
|
|
60
40
|
class TaggedParagraphForm(BaseModel):
|
61
|
-
paragraph: Annotated[str, TextareaWidget] = Field(...)
|
62
|
-
tags: Annotated[Sequence[str], TextareaWidget] = Field(
|
41
|
+
paragraph: Annotated[str, CustomWidget(TextareaWidget)] = Field(...)
|
42
|
+
tags: Annotated[Sequence[str], CustomWidget(TextareaWidget)] = Field(
|
63
43
|
default_factory=list,
|
64
44
|
title="Tags",
|
65
45
|
description="One tag per line",
|
@@ -69,43 +49,24 @@ class TextareaWidget(Widget[Sequence[str]]):
|
|
69
49
|
def split(cls, s: Any) -> Sequence[str]:
|
70
50
|
return s.split() if s else []
|
71
51
|
```
|
72
|
-
|
73
|
-
:param name: input name.
|
74
|
-
:param title: title for the widget.
|
75
|
-
:param hint: hint for human.
|
76
|
-
:param aria_label: html input aria-label value.
|
77
|
-
:param placeholder: html input placeholder value.
|
78
|
-
:param error: error of the value if any.
|
79
|
-
:param value: current value.
|
80
|
-
:param removable: display a button to remove the widget for optional fields.
|
81
|
-
:param token: token used to get unique id on the form.
|
82
|
-
|
83
52
|
"""
|
84
53
|
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
hint=hint,
|
103
|
-
aria_label=aria_label,
|
104
|
-
token=token,
|
105
|
-
error=error,
|
106
|
-
removable=removable,
|
107
|
-
)
|
108
|
-
self.placeholder = placeholder or ""
|
54
|
+
template = """
|
55
|
+
<pydantic_form.Widget :widget_id="id" :removable="removable">
|
56
|
+
<div class="pt-4">
|
57
|
+
<Label :for="id">{{title}}</Label>
|
58
|
+
<pydantic_form.Error :text="error" />
|
59
|
+
<Textarea :name="name" :id="id" :aria-label="aria_label">
|
60
|
+
{%- if v is string -%}
|
61
|
+
{{- v -}}}
|
62
|
+
{%- else -%}
|
63
|
+
{%- for v in value %}{{v}}
|
64
|
+
{% endfor -%}
|
65
|
+
{% endif %}
|
66
|
+
</Textarea>
|
67
|
+
<pydantic_form.Hint :text="hint" />
|
68
|
+
</div>
|
69
|
+
</pydantic_form.Widget>
|
70
|
+
"""
|
109
71
|
|
110
|
-
|
111
|
-
return "pydantic_form.Textarea.jinja"
|
72
|
+
placeholder: str = Field(default="")
|