fastlifeweb 0.3.0__py3-none-any.whl → 0.4.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.
- fastlife/configurator/registry.py +2 -2
- fastlife/configurator/settings.py +3 -0
- fastlife/templates/A.jinja +30 -4
- fastlife/templates/Button.jinja +31 -17
- fastlife/templates/Checkbox.jinja +20 -2
- fastlife/templates/CsrfToken.jinja +0 -1
- fastlife/templates/Form.jinja +9 -3
- fastlife/templates/H1.jinja +15 -4
- fastlife/templates/H2.jinja +15 -4
- fastlife/templates/H3.jinja +14 -0
- fastlife/templates/H4.jinja +14 -0
- fastlife/templates/H5.jinja +14 -0
- fastlife/templates/H6.jinja +14 -0
- fastlife/templates/Input.jinja +29 -6
- fastlife/templates/Label.jinja +10 -2
- fastlife/templates/Option.jinja +4 -2
- fastlife/templates/P.jinja +9 -0
- fastlife/templates/Radio.jinja +29 -7
- fastlife/templates/Select.jinja +25 -6
- fastlife/templates/pydantic_form/Boolean.jinja +1 -1
- fastlife/templates/pydantic_form/Dropdown.jinja +4 -6
- fastlife/templates/pydantic_form/Hint.jinja +4 -0
- fastlife/templates/pydantic_form/Model.jinja +1 -1
- fastlife/templates/pydantic_form/Sequence.jinja +3 -3
- fastlife/templates/pydantic_form/Text.jinja +1 -3
- fastlife/templates/pydantic_form/Union.jinja +5 -5
- fastlife/templating/__init__.py +2 -2
- fastlife/templating/binding.py +4 -22
- fastlife/templating/renderer/__init__.py +5 -2
- fastlife/templating/renderer/abstract.py +7 -5
- fastlife/templating/renderer/jinjax.py +51 -23
- fastlife/templating/renderer/widgets/base.py +12 -2
- fastlife/templating/renderer/widgets/boolean.py +4 -3
- fastlife/templating/renderer/widgets/dropdown.py +7 -6
- fastlife/templating/renderer/widgets/factory.py +38 -21
- fastlife/templating/renderer/widgets/hidden.py +2 -3
- fastlife/templating/renderer/widgets/model.py +7 -6
- fastlife/templating/renderer/widgets/sequence.py +8 -9
- fastlife/templating/renderer/widgets/text.py +9 -5
- fastlife/templating/renderer/widgets/union.py +7 -6
- fastlife/testing/testclient.py +116 -55
- fastlife/views/pydantic_form.py +5 -2
- {fastlifeweb-0.3.0.dist-info → fastlifeweb-0.4.0.dist-info}/METADATA +6 -5
- fastlifeweb-0.4.0.dist-info/RECORD +69 -0
- fastlifeweb-0.3.0.dist-info/RECORD +0 -63
- {fastlifeweb-0.3.0.dist-info → fastlifeweb-0.4.0.dist-info}/LICENSE +0 -0
- {fastlifeweb-0.3.0.dist-info → fastlifeweb-0.4.0.dist-info}/WHEEL +0 -0
@@ -12,7 +12,7 @@ if TYPE_CHECKING:
|
|
12
12
|
|
13
13
|
from fastlife.shared_utils.resolver import resolve_path
|
14
14
|
|
15
|
-
from .abstract import AbstractTemplateRenderer
|
15
|
+
from .abstract import AbstractTemplateRenderer, AbstractTemplateRendererFactory
|
16
16
|
|
17
17
|
|
18
18
|
def build_searchpath(template_search_path: str) -> Sequence[str]:
|
@@ -27,33 +27,35 @@ def build_searchpath(template_search_path: str) -> Sequence[str]:
|
|
27
27
|
return searchpath
|
28
28
|
|
29
29
|
|
30
|
-
class
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
self.
|
40
|
-
|
41
|
-
|
30
|
+
class JinjaxRenderer(AbstractTemplateRenderer):
|
31
|
+
def __init__(
|
32
|
+
self,
|
33
|
+
catalog: Catalog,
|
34
|
+
request: Request,
|
35
|
+
csrf_token_name: str,
|
36
|
+
form_data_model_prefix: str,
|
37
|
+
route_prefix: str,
|
38
|
+
):
|
39
|
+
self.route_prefix = route_prefix
|
40
|
+
self.catalog = catalog
|
41
|
+
self.request = request
|
42
|
+
self.csrf_token_name = csrf_token_name
|
43
|
+
self.form_data_model_prefix = form_data_model_prefix
|
42
44
|
|
43
|
-
def
|
44
|
-
return
|
45
|
-
|
46
|
-
|
47
|
-
csrf_token={
|
45
|
+
def build_globals(self) -> Mapping[str, Any]:
|
46
|
+
return {
|
47
|
+
"request": self.request,
|
48
|
+
"csrf_token": {
|
48
49
|
"name": self.csrf_token_name,
|
49
|
-
"value": request.scope.get(self.csrf_token_name, ""),
|
50
|
+
"value": self.request.scope.get(self.csrf_token_name, ""),
|
50
51
|
},
|
51
|
-
pydantic_form
|
52
|
-
|
53
|
-
)
|
52
|
+
"pydantic_form": self.pydantic_form,
|
53
|
+
}
|
54
54
|
|
55
55
|
def render_template(self, template: str, **params: Any) -> str:
|
56
|
-
return self.catalog.render(
|
56
|
+
return self.catalog.render( # type: ignore
|
57
|
+
template, __globals=self.build_globals(), **params
|
58
|
+
)
|
57
59
|
|
58
60
|
def pydantic_form(
|
59
61
|
self,
|
@@ -71,3 +73,29 @@ class JinjaxTemplateRenderer(AbstractTemplateRenderer):
|
|
71
73
|
removable=removable,
|
72
74
|
field=field,
|
73
75
|
)
|
76
|
+
|
77
|
+
|
78
|
+
class JinjaxTemplateRenderer(AbstractTemplateRendererFactory):
|
79
|
+
route_prefix: str
|
80
|
+
"""Used to prefix url to fetch fast life widgets."""
|
81
|
+
|
82
|
+
def __init__(self, settings: "Settings") -> None:
|
83
|
+
self.route_prefix = settings.fastlife_route_prefix
|
84
|
+
self.form_data_model_prefix = settings.form_data_model_prefix
|
85
|
+
self.csrf_token_name = settings.csrf_token_name
|
86
|
+
|
87
|
+
self.catalog = Catalog(
|
88
|
+
use_cache=settings.jinjax_use_cache,
|
89
|
+
auto_reload=settings.jinjax_auto_reload,
|
90
|
+
)
|
91
|
+
for path in build_searchpath(settings.template_search_path):
|
92
|
+
self.catalog.add_folder(path)
|
93
|
+
|
94
|
+
def __call__(self, request: Request) -> AbstractTemplateRenderer:
|
95
|
+
return JinjaxRenderer(
|
96
|
+
self.catalog,
|
97
|
+
request,
|
98
|
+
self.csrf_token_name,
|
99
|
+
self.form_data_model_prefix,
|
100
|
+
self.route_prefix,
|
101
|
+
)
|
@@ -1,12 +1,14 @@
|
|
1
1
|
import abc
|
2
2
|
import secrets
|
3
|
-
from typing import Any, Mapping, Optional, Type
|
3
|
+
from typing import Any, Generic, Mapping, Optional, Type, TypeVar
|
4
4
|
|
5
5
|
from markupsafe import Markup
|
6
6
|
|
7
7
|
from fastlife.shared_utils.infer import is_union
|
8
8
|
from fastlife.templating.renderer.abstract import AbstractTemplateRenderer
|
9
9
|
|
10
|
+
T = TypeVar("T")
|
11
|
+
|
10
12
|
|
11
13
|
def get_title(typ: Type[Any]) -> str:
|
12
14
|
return getattr(
|
@@ -16,7 +18,7 @@ def get_title(typ: Type[Any]) -> str:
|
|
16
18
|
)
|
17
19
|
|
18
20
|
|
19
|
-
class Widget(abc.ABC):
|
21
|
+
class Widget(abc.ABC, Generic[T]):
|
20
22
|
name: str
|
21
23
|
"variable name, nested variables have dots"
|
22
24
|
title: str
|
@@ -32,12 +34,14 @@ class Widget(abc.ABC):
|
|
32
34
|
self,
|
33
35
|
name: str,
|
34
36
|
*,
|
37
|
+
value: Optional[T] = None,
|
35
38
|
title: Optional[str] = None,
|
36
39
|
token: Optional[str] = None,
|
37
40
|
aria_label: Optional[str] = None,
|
38
41
|
removable: bool = False,
|
39
42
|
):
|
40
43
|
self.name = name
|
44
|
+
self.value = value
|
41
45
|
self.title = title or name.split(".")[-1]
|
42
46
|
self.aria_label = aria_label or ""
|
43
47
|
self.token = token or secrets.token_urlsafe(4).replace("_", "-")
|
@@ -79,6 +83,12 @@ class TypeWrapper:
|
|
79
83
|
def fullname(self) -> str:
|
80
84
|
return _get_fullname(self.typ)
|
81
85
|
|
86
|
+
@property
|
87
|
+
def id(self) -> str:
|
88
|
+
name = self.name.replace("_", "-").replace(".", "-").replace(":", "-")
|
89
|
+
typ = self.typ.__name__.replace("_", "-")
|
90
|
+
return f"{name}-{typ}-{self.token}"
|
91
|
+
|
82
92
|
@property
|
83
93
|
def params(self) -> Mapping[str, str]:
|
84
94
|
return {"name": self.name, "token": self.token, "title": self.title}
|
@@ -3,7 +3,7 @@ from typing import Optional
|
|
3
3
|
from .base import Widget
|
4
4
|
|
5
5
|
|
6
|
-
class BooleanWidget(Widget):
|
6
|
+
class BooleanWidget(Widget[bool]):
|
7
7
|
def __init__(
|
8
8
|
self,
|
9
9
|
name: str,
|
@@ -13,8 +13,9 @@ class BooleanWidget(Widget):
|
|
13
13
|
removable: bool = False,
|
14
14
|
token: str,
|
15
15
|
) -> None:
|
16
|
-
super().__init__(
|
17
|
-
|
16
|
+
super().__init__(
|
17
|
+
name, title=title, value=value, removable=removable, token=token
|
18
|
+
)
|
18
19
|
|
19
20
|
def get_template(self) -> str:
|
20
21
|
return "pydantic_form.Boolean"
|
@@ -3,27 +3,28 @@ from typing import Optional, Sequence, Tuple
|
|
3
3
|
from .base import Widget
|
4
4
|
|
5
5
|
|
6
|
-
class DropDownWidget(Widget):
|
6
|
+
class DropDownWidget(Widget[str]):
|
7
7
|
def __init__(
|
8
8
|
self,
|
9
9
|
name: str,
|
10
10
|
*,
|
11
11
|
title: Optional[str],
|
12
|
+
value: Optional[str] = None,
|
12
13
|
options: Sequence[Tuple[str, str]] | Sequence[str],
|
13
14
|
removable: bool = False,
|
14
|
-
value: str = "",
|
15
15
|
token: Optional[str] = None,
|
16
|
-
|
16
|
+
hint: Optional[str] = None,
|
17
17
|
) -> None:
|
18
|
-
super().__init__(
|
18
|
+
super().__init__(
|
19
|
+
name, value=value, title=title, token=token, removable=removable
|
20
|
+
)
|
19
21
|
self.options: list[dict[str, str]] = []
|
20
22
|
for opt in options:
|
21
23
|
if isinstance(opt, tuple):
|
22
24
|
self.options.append({"value": opt[0], "text": opt[1]})
|
23
25
|
else:
|
24
26
|
self.options.append({"value": opt, "text": opt})
|
25
|
-
self.
|
26
|
-
self.help_text = help_text
|
27
|
+
self.hint = hint
|
27
28
|
|
28
29
|
def get_template(self) -> str:
|
29
30
|
return "pydantic_form.Dropdown"
|
@@ -2,7 +2,7 @@ import secrets
|
|
2
2
|
from collections.abc import MutableSequence, Sequence
|
3
3
|
from decimal import Decimal
|
4
4
|
from types import NoneType
|
5
|
-
from typing import Any, Literal, Mapping, Optional, Type, get_origin
|
5
|
+
from typing import Any, Literal, Mapping, Optional, Type, cast, get_origin
|
6
6
|
from uuid import UUID
|
7
7
|
|
8
8
|
from markupsafe import Markup
|
@@ -48,7 +48,7 @@ class WidgetFactory:
|
|
48
48
|
prefix: str,
|
49
49
|
removable: bool,
|
50
50
|
field: FieldInfo | None = None,
|
51
|
-
) -> Widget:
|
51
|
+
) -> Widget[Any]:
|
52
52
|
return self.build(
|
53
53
|
base,
|
54
54
|
value=form_data.get(prefix, {}),
|
@@ -65,7 +65,22 @@ class WidgetFactory:
|
|
65
65
|
value: Any,
|
66
66
|
removable: bool,
|
67
67
|
field: FieldInfo | None = None,
|
68
|
-
) -> Widget:
|
68
|
+
) -> Widget[Any]:
|
69
|
+
if field and field.metadata:
|
70
|
+
for widget in field.metadata:
|
71
|
+
if issubclass(widget, Widget):
|
72
|
+
return cast(
|
73
|
+
Widget[Any],
|
74
|
+
widget(
|
75
|
+
name,
|
76
|
+
value=value,
|
77
|
+
removable=removable,
|
78
|
+
title=field.title if field else "",
|
79
|
+
aria_label=field.description if field else None,
|
80
|
+
token=self.token,
|
81
|
+
),
|
82
|
+
)
|
83
|
+
|
69
84
|
type_origin = get_origin(typ)
|
70
85
|
if type_origin:
|
71
86
|
if is_union(typ):
|
@@ -87,7 +102,7 @@ class WidgetFactory:
|
|
87
102
|
if issubclass(typ, bool):
|
88
103
|
return self.build_boolean(name, typ, field, value or False, removable)
|
89
104
|
|
90
|
-
if issubclass(typ, EmailStr):
|
105
|
+
if issubclass(typ, EmailStr): # type: ignore
|
91
106
|
return self.build_emailtype(name, typ, field, value or "", removable)
|
92
107
|
|
93
108
|
if issubclass(typ, SecretStr):
|
@@ -96,7 +111,7 @@ class WidgetFactory:
|
|
96
111
|
if issubclass(typ, (int, str, float, Decimal, UUID)):
|
97
112
|
return self.build_simpletype(name, typ, field, value or "", removable)
|
98
113
|
|
99
|
-
raise NotImplementedError(f"{typ} not implemented")
|
114
|
+
raise NotImplementedError(f"{typ} not implemented") # coverage: ignore
|
100
115
|
|
101
116
|
def build_model(
|
102
117
|
self,
|
@@ -105,14 +120,16 @@ class WidgetFactory:
|
|
105
120
|
field: Optional[FieldInfo],
|
106
121
|
value: Mapping[str, Any],
|
107
122
|
removable: bool,
|
108
|
-
) -> Widget:
|
123
|
+
) -> Widget[Any]:
|
109
124
|
ret: dict[str, Any] = {}
|
110
125
|
for key, field in typ.model_fields.items():
|
111
126
|
child_key = f"{field_name}.{key}" if field_name else key
|
112
127
|
if field.exclude:
|
113
128
|
continue
|
114
129
|
if field.annotation is None:
|
115
|
-
raise ValueError(
|
130
|
+
raise ValueError( # coverage: ignore
|
131
|
+
f"Missing annotation for {field} in {child_key}"
|
132
|
+
)
|
116
133
|
ret[key] = self.build(
|
117
134
|
field.annotation,
|
118
135
|
name=child_key,
|
@@ -122,7 +139,7 @@ class WidgetFactory:
|
|
122
139
|
)
|
123
140
|
return ModelWidget(
|
124
141
|
field_name,
|
125
|
-
|
142
|
+
value=list(ret.values()),
|
126
143
|
removable=removable,
|
127
144
|
title=get_title(typ),
|
128
145
|
token=self.token,
|
@@ -135,7 +152,7 @@ class WidgetFactory:
|
|
135
152
|
field: Optional[FieldInfo],
|
136
153
|
value: Any,
|
137
154
|
removable: bool,
|
138
|
-
) -> Widget:
|
155
|
+
) -> Widget[Any]:
|
139
156
|
types: list[Type[Any]] = []
|
140
157
|
# required = True
|
141
158
|
for typ in field_type.__args__: # type: ignore
|
@@ -172,7 +189,7 @@ class WidgetFactory:
|
|
172
189
|
widget = UnionWidget(
|
173
190
|
field_name,
|
174
191
|
# we assume those types are BaseModel
|
175
|
-
|
192
|
+
value=child,
|
176
193
|
children_types=types, # type: ignore
|
177
194
|
title=field.title if field else "",
|
178
195
|
token=self.token,
|
@@ -188,7 +205,7 @@ class WidgetFactory:
|
|
188
205
|
field: Optional[FieldInfo],
|
189
206
|
value: Optional[Sequence[Any]],
|
190
207
|
removable: bool,
|
191
|
-
) -> Widget:
|
208
|
+
) -> Widget[Any]:
|
192
209
|
typ = field_type.__args__[0] # type: ignore
|
193
210
|
value = value or []
|
194
211
|
items = [
|
@@ -203,9 +220,9 @@ class WidgetFactory:
|
|
203
220
|
]
|
204
221
|
return SequenceWidget(
|
205
222
|
field_name,
|
206
|
-
|
223
|
+
hint=field.description if field else "",
|
207
224
|
title=field.title if field else "",
|
208
|
-
|
225
|
+
value=items,
|
209
226
|
item_type=typ, # type: ignore
|
210
227
|
token=self.token,
|
211
228
|
removable=removable,
|
@@ -218,7 +235,7 @@ class WidgetFactory:
|
|
218
235
|
field: FieldInfo | None,
|
219
236
|
value: bool,
|
220
237
|
removable: bool,
|
221
|
-
) -> Widget:
|
238
|
+
) -> Widget[Any]:
|
222
239
|
return BooleanWidget(
|
223
240
|
field_name,
|
224
241
|
removable=removable,
|
@@ -234,10 +251,10 @@ class WidgetFactory:
|
|
234
251
|
field: FieldInfo | None,
|
235
252
|
value: str | int | float,
|
236
253
|
removable: bool,
|
237
|
-
) -> Widget:
|
254
|
+
) -> Widget[Any]:
|
238
255
|
return TextWidget(
|
239
256
|
field_name,
|
240
|
-
|
257
|
+
hint=field.description if field else "",
|
241
258
|
input_type="email",
|
242
259
|
placeholder=str(field.examples[0]) if field and field.examples else None,
|
243
260
|
removable=removable,
|
@@ -253,10 +270,10 @@ class WidgetFactory:
|
|
253
270
|
field: FieldInfo | None,
|
254
271
|
value: SecretStr | str,
|
255
272
|
removable: bool,
|
256
|
-
) -> Widget:
|
273
|
+
) -> Widget[Any]:
|
257
274
|
return TextWidget(
|
258
275
|
field_name,
|
259
|
-
|
276
|
+
hint=field.description if field else "",
|
260
277
|
input_type="password",
|
261
278
|
placeholder=str(field.examples[0]) if field and field.examples else None,
|
262
279
|
removable=removable,
|
@@ -272,7 +289,7 @@ class WidgetFactory:
|
|
272
289
|
field: FieldInfo | None,
|
273
290
|
value: str | int | float,
|
274
291
|
removable: bool,
|
275
|
-
) -> Widget:
|
292
|
+
) -> Widget[Any]:
|
276
293
|
choices: list[str] = field_type.__args__ # type: ignore
|
277
294
|
if len(choices) == 1:
|
278
295
|
return HiddenWidget(
|
@@ -296,10 +313,10 @@ class WidgetFactory:
|
|
296
313
|
field: FieldInfo | None,
|
297
314
|
value: str | int | float,
|
298
315
|
removable: bool,
|
299
|
-
) -> Widget:
|
316
|
+
) -> Widget[Any]:
|
300
317
|
return TextWidget(
|
301
318
|
field_name,
|
302
|
-
|
319
|
+
hint=field.description if field else None,
|
303
320
|
placeholder=str(field.examples[0]) if field and field.examples else None,
|
304
321
|
aria_label=field.description if field else None,
|
305
322
|
removable=removable,
|
@@ -1,7 +1,7 @@
|
|
1
1
|
from .base import Widget
|
2
2
|
|
3
3
|
|
4
|
-
class HiddenWidget(Widget):
|
4
|
+
class HiddenWidget(Widget[str]):
|
5
5
|
def __init__(
|
6
6
|
self,
|
7
7
|
name: str,
|
@@ -9,8 +9,7 @@ class HiddenWidget(Widget):
|
|
9
9
|
value: str,
|
10
10
|
token: str,
|
11
11
|
) -> None:
|
12
|
-
super().__init__(name, token=token)
|
13
|
-
self.value = value
|
12
|
+
super().__init__(name, value=value, token=token)
|
14
13
|
|
15
14
|
def get_template(self) -> str:
|
16
15
|
return "pydantic_form.Hidden"
|
@@ -1,4 +1,4 @@
|
|
1
|
-
from typing import Sequence
|
1
|
+
from typing import Any, Sequence
|
2
2
|
|
3
3
|
from markupsafe import Markup
|
4
4
|
|
@@ -7,25 +7,26 @@ from fastlife.templating.renderer.abstract import AbstractTemplateRenderer
|
|
7
7
|
from .base import Widget
|
8
8
|
|
9
9
|
|
10
|
-
class ModelWidget(Widget):
|
10
|
+
class ModelWidget(Widget[Sequence[Widget[Any]]]):
|
11
11
|
def __init__(
|
12
12
|
self,
|
13
13
|
name: str,
|
14
14
|
*,
|
15
|
-
|
15
|
+
value: Sequence[Widget[Any]],
|
16
16
|
removable: bool,
|
17
17
|
title: str,
|
18
18
|
token: str,
|
19
19
|
):
|
20
|
-
super().__init__(
|
21
|
-
|
20
|
+
super().__init__(
|
21
|
+
name, title=title, value=value, removable=removable, token=token
|
22
|
+
)
|
22
23
|
|
23
24
|
def get_template(self) -> str:
|
24
25
|
return "pydantic_form.Model"
|
25
26
|
|
26
27
|
def to_html(self, renderer: AbstractTemplateRenderer) -> Markup:
|
27
28
|
"""Return the html version"""
|
28
|
-
children_widget = [child.to_html(renderer) for child in self.
|
29
|
+
children_widget = [child.to_html(renderer) for child in self.value or []]
|
29
30
|
kwargs = {
|
30
31
|
"widget": self,
|
31
32
|
"children_widget": children_widget,
|
@@ -7,24 +7,23 @@ from fastlife.templating.renderer.abstract import AbstractTemplateRenderer
|
|
7
7
|
from .base import TypeWrapper, Widget
|
8
8
|
|
9
9
|
|
10
|
-
class SequenceWidget(Widget):
|
11
|
-
items: Sequence[Widget]
|
12
|
-
|
10
|
+
class SequenceWidget(Widget[Sequence[Widget[Any]]]):
|
13
11
|
def __init__(
|
14
12
|
self,
|
15
13
|
name: str,
|
16
14
|
*,
|
17
15
|
title: Optional[str],
|
18
|
-
|
19
|
-
|
16
|
+
hint: Optional[str],
|
17
|
+
value: Optional[Sequence[Widget[Any]]],
|
20
18
|
item_type: Type[Any],
|
21
19
|
token: str,
|
22
20
|
removable: bool,
|
23
21
|
):
|
24
|
-
super().__init__(
|
25
|
-
|
22
|
+
super().__init__(
|
23
|
+
name, value=value, title=title, token=token, removable=removable
|
24
|
+
)
|
26
25
|
self.item_type = item_type
|
27
|
-
self.
|
26
|
+
self.hint = hint
|
28
27
|
|
29
28
|
def get_template(self) -> str:
|
30
29
|
return "pydantic_form/Sequence"
|
@@ -34,7 +33,7 @@ class SequenceWidget(Widget):
|
|
34
33
|
|
35
34
|
def to_html(self, renderer: "AbstractTemplateRenderer") -> Markup:
|
36
35
|
"""Return the html version"""
|
37
|
-
children = [Markup(item.to_html(renderer)) for item in self.
|
36
|
+
children = [Markup(item.to_html(renderer)) for item in self.value or []]
|
38
37
|
return Markup(
|
39
38
|
renderer.render_template(
|
40
39
|
self.get_template(),
|
@@ -3,7 +3,7 @@ from typing import Optional
|
|
3
3
|
from .base import Widget
|
4
4
|
|
5
5
|
|
6
|
-
class TextWidget(Widget):
|
6
|
+
class TextWidget(Widget[str]):
|
7
7
|
def __init__(
|
8
8
|
self,
|
9
9
|
name: str,
|
@@ -14,15 +14,19 @@ class TextWidget(Widget):
|
|
14
14
|
removable: bool = False,
|
15
15
|
value: str = "",
|
16
16
|
token: Optional[str] = None,
|
17
|
-
|
17
|
+
hint: Optional[str] = None,
|
18
18
|
input_type: str = "text"
|
19
19
|
) -> None:
|
20
20
|
super().__init__(
|
21
|
-
name,
|
21
|
+
name,
|
22
|
+
value=value,
|
23
|
+
title=title,
|
24
|
+
aria_label=aria_label,
|
25
|
+
token=token,
|
26
|
+
removable=removable,
|
22
27
|
)
|
23
28
|
self.placeholder = placeholder or ""
|
24
|
-
self.
|
25
|
-
self.help_text = help_text
|
29
|
+
self.hint = hint
|
26
30
|
self.input_type = input_type
|
27
31
|
|
28
32
|
def get_template(self) -> str:
|
@@ -1,4 +1,4 @@
|
|
1
|
-
from typing import Optional, Sequence, Type, Union
|
1
|
+
from typing import Any, Optional, Sequence, Type, Union
|
2
2
|
|
3
3
|
from markupsafe import Markup
|
4
4
|
from pydantic import BaseModel
|
@@ -8,19 +8,20 @@ from fastlife.templating.renderer.abstract import AbstractTemplateRenderer
|
|
8
8
|
from .base import TypeWrapper, Widget
|
9
9
|
|
10
10
|
|
11
|
-
class UnionWidget(Widget):
|
11
|
+
class UnionWidget(Widget[Widget[Any]]):
|
12
12
|
def __init__(
|
13
13
|
self,
|
14
14
|
name: str,
|
15
15
|
*,
|
16
16
|
title: Optional[str],
|
17
|
-
|
17
|
+
value: Optional[Widget[Any]],
|
18
18
|
children_types: Sequence[Type[BaseModel]],
|
19
19
|
token: str,
|
20
20
|
removable: bool,
|
21
21
|
):
|
22
|
-
super().__init__(
|
23
|
-
|
22
|
+
super().__init__(
|
23
|
+
name, value=value, title=title, token=token, removable=removable
|
24
|
+
)
|
24
25
|
self.children_types = children_types
|
25
26
|
self.parent_name = name
|
26
27
|
|
@@ -35,7 +36,7 @@ class UnionWidget(Widget):
|
|
35
36
|
|
36
37
|
def to_html(self, renderer: "AbstractTemplateRenderer") -> Markup:
|
37
38
|
"""Return the html version"""
|
38
|
-
child = Markup(self.
|
39
|
+
child = Markup(self.value.to_html(renderer)) if self.value else ""
|
39
40
|
return Markup(
|
40
41
|
renderer.render_template(
|
41
42
|
self.get_template(),
|