fastlifeweb 0.9.7__py3-none-any.whl → 0.10.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/configurator.py +1 -1
- fastlife/request/{model_result.py → form.py} +18 -8
- fastlife/templates/Form.jinja +3 -2
- fastlife/templates/Textarea.jinja +11 -0
- fastlife/templates/pydantic_form/Model.jinja +7 -1
- fastlife/templates/pydantic_form/Sequence.jinja +5 -4
- fastlife/templates/pydantic_form/Textarea.jinja +10 -0
- fastlife/templates/pydantic_form/Widget.jinja +1 -1
- fastlife/templating/renderer/abstract.py +2 -2
- fastlife/templating/renderer/jinjax.py +2 -2
- fastlife/templating/renderer/widgets/base.py +4 -0
- fastlife/templating/renderer/widgets/boolean.py +4 -0
- fastlife/templating/renderer/widgets/checklist.py +4 -0
- fastlife/templating/renderer/widgets/dropdown.py +4 -2
- fastlife/templating/renderer/widgets/factory.py +78 -17
- fastlife/templating/renderer/widgets/hidden.py +2 -0
- fastlife/templating/renderer/widgets/model.py +6 -0
- fastlife/templating/renderer/widgets/sequence.py +7 -5
- fastlife/templating/renderer/widgets/text.py +32 -2
- fastlife/templating/renderer/widgets/union.py +4 -0
- {fastlifeweb-0.9.7.dist-info → fastlifeweb-0.10.0.dist-info}/METADATA +1 -1
- {fastlifeweb-0.9.7.dist-info → fastlifeweb-0.10.0.dist-info}/RECORD +24 -22
- {fastlifeweb-0.9.7.dist-info → fastlifeweb-0.10.0.dist-info}/LICENSE +0 -0
- {fastlifeweb-0.9.7.dist-info → fastlifeweb-0.10.0.dist-info}/WHEEL +0 -0
@@ -11,7 +11,7 @@ T = TypeVar("T", bound=BaseModel)
|
|
11
11
|
"""Template type for form serialized model"""
|
12
12
|
|
13
13
|
|
14
|
-
class
|
14
|
+
class FormModel(Generic[T]):
|
15
15
|
prefix: str
|
16
16
|
model: T
|
17
17
|
errors: Mapping[str, str]
|
@@ -26,9 +26,19 @@ class ModelResult(Generic[T]):
|
|
26
26
|
self.is_valid = is_valid
|
27
27
|
|
28
28
|
@classmethod
|
29
|
-
def default(cls, prefix: str, pydantic_type: Type[T]) -> "
|
29
|
+
def default(cls, prefix: str, pydantic_type: Type[T]) -> "FormModel[T]":
|
30
30
|
return cls(prefix, pydantic_type.model_construct(), {})
|
31
31
|
|
32
|
+
def edit(self, pydantic_type: T) -> None:
|
33
|
+
"""
|
34
|
+
Load the form with the given model and consider it as valid for the user.
|
35
|
+
|
36
|
+
No error will be reported.
|
37
|
+
"""
|
38
|
+
self.model = pydantic_type
|
39
|
+
self.errors = {}
|
40
|
+
self.is_valid = True
|
41
|
+
|
32
42
|
@property
|
33
43
|
def form_data(self) -> Mapping[str, Any]:
|
34
44
|
return {self.prefix: self.model.model_dump()}
|
@@ -36,7 +46,7 @@ class ModelResult(Generic[T]):
|
|
36
46
|
@classmethod
|
37
47
|
def from_payload(
|
38
48
|
cls, prefix: str, pydantic_type: Type[T], data: Mapping[str, Any]
|
39
|
-
) -> "
|
49
|
+
) -> "FormModel[T]":
|
40
50
|
try:
|
41
51
|
return cls(prefix, pydantic_type(**data.get(prefix, {})), {}, True)
|
42
52
|
except ValidationError as exc:
|
@@ -75,17 +85,17 @@ class ModelResult(Generic[T]):
|
|
75
85
|
return cls(prefix, model, errors)
|
76
86
|
|
77
87
|
|
78
|
-
def
|
88
|
+
def form_model(
|
79
89
|
cls: Type[T], name: str | None = None
|
80
|
-
) -> Callable[[Mapping[str, Any]],
|
90
|
+
) -> Callable[[Mapping[str, Any]], FormModel[T]]:
|
81
91
|
"""
|
82
92
|
Build a model, a class of type T based on Pydandic Base Model from a form payload.
|
83
93
|
"""
|
84
94
|
|
85
|
-
def to_model(data: MappingFormData, registry: Registry) ->
|
95
|
+
def to_model(data: MappingFormData, registry: Registry) -> FormModel[T]:
|
86
96
|
prefix = name or registry.settings.form_data_model_prefix
|
87
97
|
if not data:
|
88
|
-
return
|
89
|
-
return
|
98
|
+
return FormModel[T].default(prefix, cls)
|
99
|
+
return FormModel[T].from_payload(prefix, cls, data)
|
90
100
|
|
91
101
|
return Depends(to_model)
|
fastlife/templates/Form.jinja
CHANGED
@@ -2,9 +2,10 @@
|
|
2
2
|
method="",
|
3
3
|
action=None,
|
4
4
|
hx_post=None,
|
5
|
+
hx_disable=None,
|
5
6
|
#}
|
6
|
-
<form class="{{attrs.class or FORM_CLASS}}" {% if hx_post is not none
|
7
|
-
%}hx-post="{% if hx_post is not true %}{{hx_post}}{% endif%}" {% endif %}{% if action is not none
|
7
|
+
<form class="{{attrs.class or FORM_CLASS}}" {% if hx_disable %}hx-disable {% else %}{% if hx_post is not none
|
8
|
+
%}hx-post="{% if hx_post is not true %}{{hx_post}}{% endif%}" {% endif %}{% endif %}{% if action is not none
|
8
9
|
%}action="{{action}}" {% endif %}{% if method %}method="{{method}}" {% endif %}>
|
9
10
|
<CsrfToken />
|
10
11
|
{{ content }}
|
@@ -0,0 +1,11 @@
|
|
1
|
+
{# def
|
2
|
+
name,
|
3
|
+
value="",
|
4
|
+
id="",
|
5
|
+
aria_label="",
|
6
|
+
placeholder="",
|
7
|
+
#}
|
8
|
+
|
9
|
+
<textarea name="{{name}}" {% if id %}id="{{id}}" {% endif %}{% if aria_label
|
10
|
+
%}aria-label="{{aria_label}}" {% endif %} {% if placeholder %}placeholder="{{placeholder}}" {% endif %}
|
11
|
+
class="{{attrs.class or INPUT_CLASS}}">{{content}}</textarea>
|
@@ -1,7 +1,8 @@
|
|
1
1
|
{# def widget, children_widget #}
|
2
2
|
|
3
3
|
<pydantic_form.Widget :widget="widget" :removable="widget.removable">
|
4
|
-
<div id="{{widget.id}}" class="m-4">
|
4
|
+
<div id="{{widget.id}}"{% if widget.nested %} class="m-4"{%endif%}>
|
5
|
+
{% if widget.nested %}
|
5
6
|
<Details>
|
6
7
|
<Summary :id="widget.id + '-summary'">
|
7
8
|
<H3 :class="H3_SUMMARY_CLASS">{{widget.title}}</H3>
|
@@ -13,5 +14,10 @@
|
|
13
14
|
{% endfor %}
|
14
15
|
</div>
|
15
16
|
</Details>
|
17
|
+
{% else %}
|
18
|
+
{% for child in children_widget %}
|
19
|
+
{{ child }}
|
20
|
+
{% endfor %}
|
21
|
+
{% endif %}
|
16
22
|
</div>
|
17
23
|
</pydantic_form.Widget>
|
@@ -18,15 +18,16 @@
|
|
18
18
|
</script>
|
19
19
|
|
20
20
|
<div id="{{widget.id}}-content" class="m-4" data-length="{{children_widgets|length|string}}">
|
21
|
-
{%
|
22
|
-
{% set container_id = widget.id + "-container" %}
|
21
|
+
{% set container_id = widget.id + "-children-container" %}
|
23
22
|
<div id="{{container_id}}">
|
23
|
+
{% for child in children_widgets %}
|
24
24
|
{{ child }}
|
25
|
+
{% endfor%}
|
25
26
|
</div>
|
26
|
-
{% endfor%}
|
27
27
|
</div>
|
28
|
+
|
28
29
|
<div>
|
29
|
-
{% set container_id = "#" + widget.id + "-container" %}
|
30
|
+
{% set container_id = "#" + widget.id + "-children-container" %}
|
30
31
|
{% set add_id = widget.id + "-add" %}
|
31
32
|
{% set vals = 'js:{"name": '+ fnGetName + '(), "token": "' + type.token + '", "removable": true}' %}
|
32
33
|
<Button type="button" :hx-target="container_id" hx-swap="beforeend" :id="add_id" :hx-vals="vals" :hx-get="type.url">
|
@@ -0,0 +1,10 @@
|
|
1
|
+
{# def widget #}
|
2
|
+
<pydantic_form.Widget :widget="widget" :removable="widget.removable">
|
3
|
+
<div class="pt-4">
|
4
|
+
<Label :for="widget.id">{{widget.title}}</Label>
|
5
|
+
<pydantic_form.Error :text="widget.error" />
|
6
|
+
<Textarea :name="widget.name" :id="widget.id" :aria-label="widget.aria_label">{% for v in widget.value%}{{v}}
|
7
|
+
{% endfor %}</Textarea>
|
8
|
+
<pydantic_form.Hint :text="widget.hint" />
|
9
|
+
</div>
|
10
|
+
</pydantic_form.Widget>
|
@@ -3,7 +3,7 @@
|
|
3
3
|
<div id="{{container_id}}">
|
4
4
|
{{ content }}
|
5
5
|
{% if removable %}
|
6
|
-
<Button type="button" :onclick={{"document.getElementById('" + container_id + "').remove()"}}>
|
6
|
+
<Button type="button" :onclick={{"document.getElementById('" + container_id + "').remove()" }}>
|
7
7
|
Remove
|
8
8
|
</Button>
|
9
9
|
{% endif %}
|
@@ -5,7 +5,7 @@ from fastapi import Request
|
|
5
5
|
from markupsafe import Markup
|
6
6
|
from pydantic.fields import FieldInfo
|
7
7
|
|
8
|
-
from fastlife.request.
|
8
|
+
from fastlife.request.form import FormModel
|
9
9
|
|
10
10
|
|
11
11
|
class AbstractTemplateRenderer(abc.ABC):
|
@@ -46,7 +46,7 @@ class AbstractTemplateRenderer(abc.ABC):
|
|
46
46
|
|
47
47
|
@abc.abstractmethod
|
48
48
|
def pydantic_form(
|
49
|
-
self, model:
|
49
|
+
self, model: FormModel[Any], *, token: Optional[str] = None
|
50
50
|
) -> Markup:
|
51
51
|
"""
|
52
52
|
Render an http form from a given model.
|
@@ -5,7 +5,7 @@ from jinjax.catalog import Catalog
|
|
5
5
|
from markupsafe import Markup
|
6
6
|
from pydantic.fields import FieldInfo
|
7
7
|
|
8
|
-
from fastlife.request.
|
8
|
+
from fastlife.request.form import FormModel
|
9
9
|
from fastlife.templating.renderer.widgets.factory import WidgetFactory
|
10
10
|
|
11
11
|
if TYPE_CHECKING:
|
@@ -69,7 +69,7 @@ class JinjaxRenderer(AbstractTemplateRenderer):
|
|
69
69
|
)
|
70
70
|
|
71
71
|
def pydantic_form(
|
72
|
-
self, model:
|
72
|
+
self, model: FormModel[Any], *, token: Optional[str] = None
|
73
73
|
) -> Markup:
|
74
74
|
return WidgetFactory(self, token).get_markup(model)
|
75
75
|
|
@@ -23,6 +23,8 @@ class Widget(abc.ABC, Generic[T]):
|
|
23
23
|
"variable name, nested variables have dots"
|
24
24
|
title: str
|
25
25
|
"Human title for the widget"
|
26
|
+
hint: str
|
27
|
+
"A help message for the the widget"
|
26
28
|
aria_label: str
|
27
29
|
"Non visible text alternative"
|
28
30
|
token: str
|
@@ -37,6 +39,7 @@ class Widget(abc.ABC, Generic[T]):
|
|
37
39
|
value: T | None = None,
|
38
40
|
error: str | None = None,
|
39
41
|
title: str | None = None,
|
42
|
+
hint: str | None = None,
|
40
43
|
token: str | None = None,
|
41
44
|
aria_label: str | None = None,
|
42
45
|
removable: bool = False,
|
@@ -45,6 +48,7 @@ class Widget(abc.ABC, Generic[T]):
|
|
45
48
|
self.value = value
|
46
49
|
self.error = error
|
47
50
|
self.title = title or name.split(".")[-1]
|
51
|
+
self.hint = hint or ""
|
48
52
|
self.aria_label = aria_label or ""
|
49
53
|
self.token = token or secrets.token_urlsafe(4).replace("_", "-")
|
50
54
|
self.removable = removable
|
@@ -7,6 +7,8 @@ class BooleanWidget(Widget[bool]):
|
|
7
7
|
name: str,
|
8
8
|
*,
|
9
9
|
title: str | None,
|
10
|
+
hint: str | None = None,
|
11
|
+
aria_label: str | None = None,
|
10
12
|
value: bool = False,
|
11
13
|
error: str | None = None,
|
12
14
|
removable: bool = False,
|
@@ -15,6 +17,8 @@ class BooleanWidget(Widget[bool]):
|
|
15
17
|
super().__init__(
|
16
18
|
name,
|
17
19
|
title=title,
|
20
|
+
hint=hint,
|
21
|
+
aria_label=aria_label,
|
18
22
|
value=value,
|
19
23
|
error=error,
|
20
24
|
removable=removable,
|
@@ -29,6 +29,8 @@ class ChecklistWidget(Widget[Sequence[Checkable]]):
|
|
29
29
|
name: str,
|
30
30
|
*,
|
31
31
|
title: str | None,
|
32
|
+
hint: str | None = None,
|
33
|
+
aria_label: str | None = None,
|
32
34
|
value: Sequence[Checkable],
|
33
35
|
error: str | None = None,
|
34
36
|
token: str,
|
@@ -40,6 +42,8 @@ class ChecklistWidget(Widget[Sequence[Checkable]]):
|
|
40
42
|
error=error,
|
41
43
|
token=token,
|
42
44
|
title=title,
|
45
|
+
hint=hint,
|
46
|
+
aria_label=aria_label,
|
43
47
|
removable=removable,
|
44
48
|
)
|
45
49
|
|
@@ -9,12 +9,13 @@ class DropDownWidget(Widget[str]):
|
|
9
9
|
name: str,
|
10
10
|
*,
|
11
11
|
title: Optional[str],
|
12
|
+
hint: Optional[str] = None,
|
13
|
+
aria_label: Optional[str] = None,
|
12
14
|
value: Optional[str] = None,
|
13
15
|
error: str | None = None,
|
14
16
|
options: Sequence[Tuple[str, str]] | Sequence[str],
|
15
17
|
removable: bool = False,
|
16
18
|
token: Optional[str] = None,
|
17
|
-
hint: Optional[str] = None,
|
18
19
|
) -> None:
|
19
20
|
super().__init__(
|
20
21
|
name,
|
@@ -23,6 +24,8 @@ class DropDownWidget(Widget[str]):
|
|
23
24
|
title=title,
|
24
25
|
token=token,
|
25
26
|
removable=removable,
|
27
|
+
hint=hint,
|
28
|
+
aria_label=aria_label,
|
26
29
|
)
|
27
30
|
self.options: list[dict[str, str]] = []
|
28
31
|
for opt in options:
|
@@ -30,7 +33,6 @@ class DropDownWidget(Widget[str]):
|
|
30
33
|
self.options.append({"value": opt[0], "text": opt[1]})
|
31
34
|
else:
|
32
35
|
self.options.append({"value": opt, "text": opt})
|
33
|
-
self.hint = hint
|
34
36
|
|
35
37
|
def get_template(self) -> str:
|
36
38
|
return "pydantic_form.Dropdown"
|
@@ -11,7 +11,7 @@ from markupsafe import Markup
|
|
11
11
|
from pydantic import BaseModel, EmailStr, SecretStr, ValidationError
|
12
12
|
from pydantic.fields import FieldInfo
|
13
13
|
|
14
|
-
from fastlife.request.
|
14
|
+
from fastlife.request.form import FormModel
|
15
15
|
from fastlife.shared_utils.infer import is_complex_type, is_union
|
16
16
|
from fastlife.templating.renderer.abstract import AbstractTemplateRenderer
|
17
17
|
from fastlife.templating.renderer.widgets.boolean import BooleanWidget
|
@@ -20,7 +20,7 @@ from fastlife.templating.renderer.widgets.dropdown import DropDownWidget
|
|
20
20
|
from fastlife.templating.renderer.widgets.hidden import HiddenWidget
|
21
21
|
from fastlife.templating.renderer.widgets.sequence import SequenceWidget
|
22
22
|
|
23
|
-
from .base import Widget
|
23
|
+
from .base import Widget
|
24
24
|
from .model import ModelWidget
|
25
25
|
from .text import TextWidget
|
26
26
|
from .union import UnionWidget
|
@@ -33,7 +33,7 @@ class WidgetFactory:
|
|
33
33
|
|
34
34
|
def get_markup(
|
35
35
|
self,
|
36
|
-
model:
|
36
|
+
model: FormModel[Any],
|
37
37
|
*,
|
38
38
|
removable: bool = False,
|
39
39
|
field: FieldInfo | None = None,
|
@@ -86,7 +86,12 @@ class WidgetFactory:
|
|
86
86
|
value=value,
|
87
87
|
removable=removable,
|
88
88
|
title=field.title if field else "",
|
89
|
-
|
89
|
+
hint=field.description if field else None,
|
90
|
+
aria_label=(
|
91
|
+
field.json_schema_extra.get("aria_label") # type:ignore
|
92
|
+
if field and field.json_schema_extra
|
93
|
+
else None
|
94
|
+
),
|
90
95
|
token=self.token,
|
91
96
|
error=form_errors.get(name),
|
92
97
|
),
|
@@ -154,18 +159,18 @@ class WidgetFactory:
|
|
154
159
|
removable: bool,
|
155
160
|
) -> Widget[Any]:
|
156
161
|
ret: dict[str, Any] = {}
|
157
|
-
for key,
|
162
|
+
for key, child_field in typ.model_fields.items():
|
158
163
|
child_key = f"{field_name}.{key}" if field_name else key
|
159
|
-
if
|
164
|
+
if child_field.exclude:
|
160
165
|
continue
|
161
|
-
if
|
166
|
+
if child_field.annotation is None:
|
162
167
|
raise ValueError( # coverage: ignore
|
163
|
-
f"Missing annotation for {
|
168
|
+
f"Missing annotation for {child_field} in {child_key}"
|
164
169
|
)
|
165
170
|
ret[key] = self.build(
|
166
|
-
|
171
|
+
child_field.annotation,
|
167
172
|
name=child_key,
|
168
|
-
field=
|
173
|
+
field=child_field,
|
169
174
|
value=value.get(key),
|
170
175
|
form_errors=form_errors,
|
171
176
|
removable=False,
|
@@ -174,9 +179,16 @@ class WidgetFactory:
|
|
174
179
|
field_name,
|
175
180
|
value=list(ret.values()),
|
176
181
|
removable=removable,
|
177
|
-
title=
|
182
|
+
title=field.title if field and field.title else "",
|
183
|
+
hint=field.description if field else None,
|
184
|
+
aria_label=(
|
185
|
+
field.json_schema_extra.get("aria_label") # type:ignore
|
186
|
+
if field and field.json_schema_extra
|
187
|
+
else None
|
188
|
+
),
|
178
189
|
token=self.token,
|
179
190
|
error=form_errors.get(field_name),
|
191
|
+
nested=field is not None,
|
180
192
|
)
|
181
193
|
|
182
194
|
def build_union(
|
@@ -233,6 +245,12 @@ class WidgetFactory:
|
|
233
245
|
value=child,
|
234
246
|
children_types=types, # type: ignore
|
235
247
|
title=field.title if field else "",
|
248
|
+
hint=field.description if field else None,
|
249
|
+
aria_label=(
|
250
|
+
field.json_schema_extra.get("aria_label") # type:ignore
|
251
|
+
if field and field.json_schema_extra
|
252
|
+
else None
|
253
|
+
),
|
236
254
|
token=self.token,
|
237
255
|
removable=removable,
|
238
256
|
error=form_errors.get(field_name),
|
@@ -264,8 +282,13 @@ class WidgetFactory:
|
|
264
282
|
]
|
265
283
|
return SequenceWidget(
|
266
284
|
field_name,
|
267
|
-
hint=field.description if field else "",
|
268
285
|
title=field.title if field else "",
|
286
|
+
hint=field.description if field else None,
|
287
|
+
aria_label=(
|
288
|
+
field.json_schema_extra.get("aria_label") # type:ignore
|
289
|
+
if field and field.json_schema_extra
|
290
|
+
else None
|
291
|
+
),
|
269
292
|
value=items,
|
270
293
|
item_type=typ, # type: ignore
|
271
294
|
token=self.token,
|
@@ -320,6 +343,12 @@ class WidgetFactory:
|
|
320
343
|
return ChecklistWidget(
|
321
344
|
field_name,
|
322
345
|
title=field.title if field else "",
|
346
|
+
hint=field.description if field else None,
|
347
|
+
aria_label=(
|
348
|
+
field.json_schema_extra.get("aria_label") # type:ignore
|
349
|
+
if field and field.json_schema_extra
|
350
|
+
else None
|
351
|
+
),
|
323
352
|
token=self.token,
|
324
353
|
value=choices,
|
325
354
|
removable=removable,
|
@@ -339,6 +368,12 @@ class WidgetFactory:
|
|
339
368
|
field_name,
|
340
369
|
removable=removable,
|
341
370
|
title=field.title if field else "",
|
371
|
+
hint=field.description if field else None,
|
372
|
+
aria_label=(
|
373
|
+
field.json_schema_extra.get("aria_label") # type:ignore
|
374
|
+
if field and field.json_schema_extra
|
375
|
+
else None
|
376
|
+
),
|
342
377
|
token=self.token,
|
343
378
|
value=value,
|
344
379
|
error=form_errors.get(field_name),
|
@@ -355,11 +390,16 @@ class WidgetFactory:
|
|
355
390
|
) -> Widget[Any]:
|
356
391
|
return TextWidget(
|
357
392
|
field_name,
|
358
|
-
hint=field.description if field else "",
|
359
393
|
input_type="email",
|
360
394
|
placeholder=str(field.examples[0]) if field and field.examples else None,
|
361
395
|
removable=removable,
|
362
396
|
title=field.title if field else "",
|
397
|
+
hint=field.description if field else None,
|
398
|
+
aria_label=(
|
399
|
+
field.json_schema_extra.get("aria_label") # type:ignore
|
400
|
+
if field and field.json_schema_extra
|
401
|
+
else None
|
402
|
+
),
|
363
403
|
token=self.token,
|
364
404
|
value=str(value),
|
365
405
|
error=form_errors.get(field_name),
|
@@ -376,11 +416,16 @@ class WidgetFactory:
|
|
376
416
|
) -> Widget[Any]:
|
377
417
|
return TextWidget(
|
378
418
|
field_name,
|
379
|
-
hint=field.description if field else "",
|
380
419
|
input_type="password",
|
381
420
|
placeholder=str(field.examples[0]) if field and field.examples else None,
|
382
421
|
removable=removable,
|
383
422
|
title=field.title if field else "",
|
423
|
+
hint=field.description if field else None,
|
424
|
+
aria_label=(
|
425
|
+
field.json_schema_extra.get("aria_label") # type:ignore
|
426
|
+
if field and field.json_schema_extra
|
427
|
+
else None
|
428
|
+
),
|
384
429
|
token=self.token,
|
385
430
|
value=value.get_secret_value() if isinstance(value, SecretStr) else value,
|
386
431
|
error=form_errors.get(field_name),
|
@@ -407,6 +452,12 @@ class WidgetFactory:
|
|
407
452
|
options=choices,
|
408
453
|
removable=removable,
|
409
454
|
title=field.title if field else "",
|
455
|
+
hint=field.description if field else None,
|
456
|
+
aria_label=(
|
457
|
+
field.json_schema_extra.get("aria_label") # type:ignore
|
458
|
+
if field and field.json_schema_extra
|
459
|
+
else None
|
460
|
+
),
|
410
461
|
token=self.token,
|
411
462
|
value=str(value),
|
412
463
|
error=form_errors.get(field_name),
|
@@ -427,6 +478,12 @@ class WidgetFactory:
|
|
427
478
|
options=options, # type: ignore
|
428
479
|
removable=removable,
|
429
480
|
title=field.title if field else "",
|
481
|
+
hint=field.description if field else None,
|
482
|
+
aria_label=(
|
483
|
+
field.json_schema_extra.get("aria_label") # type:ignore
|
484
|
+
if field and field.json_schema_extra
|
485
|
+
else None
|
486
|
+
),
|
430
487
|
token=self.token,
|
431
488
|
value=str(value),
|
432
489
|
error=form_errors.get(field_name),
|
@@ -443,11 +500,15 @@ class WidgetFactory:
|
|
443
500
|
) -> Widget[Any]:
|
444
501
|
return TextWidget(
|
445
502
|
field_name,
|
446
|
-
hint=field.description if field else None,
|
447
503
|
placeholder=str(field.examples[0]) if field and field.examples else None,
|
448
|
-
aria_label=field.description if field else None,
|
449
|
-
removable=removable,
|
450
504
|
title=field.title if field else "",
|
505
|
+
hint=field.description if field else None,
|
506
|
+
aria_label=(
|
507
|
+
field.json_schema_extra.get("aria_label") # type:ignore
|
508
|
+
if field and field.json_schema_extra
|
509
|
+
else None
|
510
|
+
),
|
511
|
+
removable=removable,
|
451
512
|
token=self.token,
|
452
513
|
value=str(value),
|
453
514
|
error=form_errors.get(field_name),
|
@@ -16,16 +16,22 @@ class ModelWidget(Widget[Sequence[Widget[Any]]]):
|
|
16
16
|
error: str | None = None,
|
17
17
|
removable: bool,
|
18
18
|
title: str,
|
19
|
+
hint: str | None = None,
|
20
|
+
aria_label: str | None = None,
|
19
21
|
token: str,
|
22
|
+
nested: bool,
|
20
23
|
):
|
21
24
|
super().__init__(
|
22
25
|
name,
|
23
26
|
title=title,
|
27
|
+
hint=hint,
|
28
|
+
aria_label=aria_label,
|
24
29
|
value=value,
|
25
30
|
error=error,
|
26
31
|
removable=removable,
|
27
32
|
token=token,
|
28
33
|
)
|
34
|
+
self.nested = nested
|
29
35
|
|
30
36
|
def get_template(self) -> str:
|
31
37
|
return "pydantic_form.Model"
|
@@ -1,4 +1,4 @@
|
|
1
|
-
from typing import Any,
|
1
|
+
from typing import Any, Sequence, Type
|
2
2
|
|
3
3
|
from markupsafe import Markup
|
4
4
|
|
@@ -12,9 +12,10 @@ class SequenceWidget(Widget[Sequence[Widget[Any]]]):
|
|
12
12
|
self,
|
13
13
|
name: str,
|
14
14
|
*,
|
15
|
-
title:
|
16
|
-
hint:
|
17
|
-
|
15
|
+
title: str | None,
|
16
|
+
hint: str | None = None,
|
17
|
+
aria_label: str | None = None,
|
18
|
+
value: Sequence[Widget[Any]] | None,
|
18
19
|
error: str | None = None,
|
19
20
|
item_type: Type[Any],
|
20
21
|
token: str,
|
@@ -25,11 +26,12 @@ class SequenceWidget(Widget[Sequence[Widget[Any]]]):
|
|
25
26
|
value=value,
|
26
27
|
error=error,
|
27
28
|
title=title,
|
29
|
+
hint=hint,
|
30
|
+
aria_label=aria_label,
|
28
31
|
token=token,
|
29
32
|
removable=removable,
|
30
33
|
)
|
31
34
|
self.item_type = item_type
|
32
|
-
self.hint = hint
|
33
35
|
|
34
36
|
def get_template(self) -> str:
|
35
37
|
return "pydantic_form/Sequence"
|
@@ -9,27 +9,57 @@ class TextWidget(Widget[str]):
|
|
9
9
|
name: str,
|
10
10
|
*,
|
11
11
|
title: Optional[str],
|
12
|
+
hint: Optional[str] = None,
|
12
13
|
aria_label: Optional[str] = None,
|
13
14
|
placeholder: Optional[str] = None,
|
14
15
|
error: str | None = None,
|
15
16
|
removable: bool = False,
|
16
17
|
value: str = "",
|
17
18
|
token: Optional[str] = None,
|
18
|
-
hint: Optional[str] = None,
|
19
19
|
input_type: str = "text",
|
20
20
|
) -> None:
|
21
21
|
super().__init__(
|
22
22
|
name,
|
23
23
|
value=value,
|
24
24
|
title=title,
|
25
|
+
hint=hint,
|
25
26
|
aria_label=aria_label,
|
26
27
|
token=token,
|
27
28
|
error=error,
|
28
29
|
removable=removable,
|
29
30
|
)
|
30
31
|
self.placeholder = placeholder or ""
|
31
|
-
self.hint = hint
|
32
32
|
self.input_type = input_type
|
33
33
|
|
34
34
|
def get_template(self) -> str:
|
35
35
|
return "pydantic_form.Text"
|
36
|
+
|
37
|
+
|
38
|
+
class TextareaWidget(Widget[str]):
|
39
|
+
def __init__(
|
40
|
+
self,
|
41
|
+
name: str,
|
42
|
+
*,
|
43
|
+
title: Optional[str],
|
44
|
+
hint: Optional[str] = None,
|
45
|
+
aria_label: Optional[str] = None,
|
46
|
+
placeholder: Optional[str] = None,
|
47
|
+
error: str | None = None,
|
48
|
+
removable: bool = False,
|
49
|
+
value: str = "",
|
50
|
+
token: Optional[str] = None,
|
51
|
+
) -> None:
|
52
|
+
super().__init__(
|
53
|
+
name,
|
54
|
+
value=value,
|
55
|
+
title=title,
|
56
|
+
hint=hint,
|
57
|
+
aria_label=aria_label,
|
58
|
+
token=token,
|
59
|
+
error=error,
|
60
|
+
removable=removable,
|
61
|
+
)
|
62
|
+
self.placeholder = placeholder or ""
|
63
|
+
|
64
|
+
def get_template(self) -> str:
|
65
|
+
return "pydantic_form.Textarea"
|
@@ -14,6 +14,8 @@ class UnionWidget(Widget[Widget[Any]]):
|
|
14
14
|
name: str,
|
15
15
|
*,
|
16
16
|
title: Optional[str],
|
17
|
+
hint: Optional[str] = None,
|
18
|
+
aria_label: Optional[str] = None,
|
17
19
|
value: Optional[Widget[Any]],
|
18
20
|
error: str | None = None,
|
19
21
|
children_types: Sequence[Type[BaseModel]],
|
@@ -25,6 +27,8 @@ class UnionWidget(Widget[Widget[Any]]):
|
|
25
27
|
value=value,
|
26
28
|
error=error,
|
27
29
|
title=title,
|
30
|
+
hint=hint,
|
31
|
+
aria_label=aria_label,
|
28
32
|
token=token,
|
29
33
|
removable=removable,
|
30
34
|
)
|
@@ -1,7 +1,7 @@
|
|
1
1
|
fastlife/__init__.py,sha256=nAeweHnTvWHgDZYuPIWawZvT4juuhvMp4hzF2B-yCJU,317
|
2
2
|
fastlife/configurator/__init__.py,sha256=vMV25HEfuXjCL7DhZukhlB2JSfxwzX2lGbErcZ5b7N0,146
|
3
3
|
fastlife/configurator/base.py,sha256=2ahvTudLmD99YQjnIeGN5JDPCSl3k-mauu7bsSEB5RE,216
|
4
|
-
fastlife/configurator/configurator.py,sha256=
|
4
|
+
fastlife/configurator/configurator.py,sha256=DbeOfecoplix35AvTBsO-WLFXb07c1kvL-1r4hLIy6U,7222
|
5
5
|
fastlife/configurator/registry.py,sha256=FKXCxWlMMGNN1-l5H6QLTCoqP_tWENN84RjkConejWI,1580
|
6
6
|
fastlife/configurator/route_handler.py,sha256=TRsiGM8xQxJvRFbdKZphLK7l37DSfKvvmkEbg-h5EoU,977
|
7
7
|
fastlife/configurator/settings.py,sha256=Go1y7dGfxG5tfdAfysSl6TruBUfD_IGMq0Ks_eFBakE,3718
|
@@ -13,8 +13,8 @@ fastlife/middlewares/session/middleware.py,sha256=JgXdBlxlm9zIEgXcidbBrMAp5wJVPs
|
|
13
13
|
fastlife/middlewares/session/serializer.py,sha256=qpVnHQjYTxw3aOnoEOKIjOFJg2z45KjiX5sipWk2gws,1458
|
14
14
|
fastlife/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
15
15
|
fastlife/request/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
16
|
+
fastlife/request/form.py,sha256=aFFdIPZUrs3i9QAwgJw6ajUWMq_A9q_gA3oMxVaXrSg,3506
|
16
17
|
fastlife/request/form_data.py,sha256=mP8ilwRUY2WbktIkRgaJJ2EUjwUMPbSPg29GzwZgT18,3713
|
17
|
-
fastlife/request/model_result.py,sha256=TRaVkyIE50IzVprncoWUUZd15-y4D3ywyZdx7eh6nFE,3237
|
18
18
|
fastlife/routing/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
19
19
|
fastlife/routing/router.py,sha256=hHK4NumgTTXI330ZxQeZBiEfGtBRn4_k_fIh_xaaVtg,338
|
20
20
|
fastlife/security/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
@@ -28,7 +28,7 @@ fastlife/templates/Button.jinja,sha256=uSDCdK2uYEsLnTUDjKo8qaUKHjddIoDvTMR6QrADO
|
|
28
28
|
fastlife/templates/Checkbox.jinja,sha256=wljKcufMxSWMCiTpqI5Ugfxn4GF224tJaw0AtM1syPo,236
|
29
29
|
fastlife/templates/CsrfToken.jinja,sha256=7qbPvgiAgR6fgaCnDTLozJgOPSWWUJ_W4VLPGra4ahA,61
|
30
30
|
fastlife/templates/Details.jinja,sha256=JP_otsatCu4w68iIqoMiCwh1S0ymUd_yFqoztdifOVU,166
|
31
|
-
fastlife/templates/Form.jinja,sha256=
|
31
|
+
fastlife/templates/Form.jinja,sha256=WUS68Z4BH3kpv2qntPALZ8X259P-FxwjNfABQmSXFgM,409
|
32
32
|
fastlife/templates/H1.jinja,sha256=bx8MnoZKN2C2GbfmX4lgi1P6XRh_g67qb_hWmrodGhs,59
|
33
33
|
fastlife/templates/H2.jinja,sha256=aY2Q_DdHHViqA2nUCPjAqlUgcAqadYnUJnxFnCONydY,59
|
34
34
|
fastlife/templates/H3.jinja,sha256=pkt2APIelNnjTE67XBgr8Futs5mAY7QzmF_isMINim8,59
|
@@ -46,6 +46,7 @@ fastlife/templates/Summary.jinja,sha256=eATV30yqsz-wASiCNaQzhNNuVfRXNoMgeyjKv4J8
|
|
46
46
|
fastlife/templates/Table.jinja,sha256=goC3qNsbt1NZgbhhuDRuoJuMKHNRCt_bmK96SY_KF1Y,74
|
47
47
|
fastlife/templates/Tbody.jinja,sha256=K8wwTYcYi8-NYdP4g7MBokvYqJp9QW7DmChADuCa-Gs,35
|
48
48
|
fastlife/templates/Td.jinja,sha256=xF2h-Vie0OnjShxzcLwjrGHgi9hvLdlq4H7Ost7aCAA,65
|
49
|
+
fastlife/templates/Textarea.jinja,sha256=q-xb58Gr-vSvs9nQVDb-vDU-5FrTNR9QDAyTw8jK5ag,312
|
49
50
|
fastlife/templates/Tfoot.jinja,sha256=iSHDcwhi7VNql5Yl3jvkrx2WVquT5Zm8to9P3WBlKYQ,35
|
50
51
|
fastlife/templates/Th.jinja,sha256=dhWdbwSWIMpIxWFOIXIX6FhRkeUwwWZr5R7PoTcJZ9w,65
|
51
52
|
fastlife/templates/Thead.jinja,sha256=gp3T4XiEk5zgih0gmmLcVke-Z8AgTol7AQrRtVev480,35
|
@@ -1669,33 +1670,34 @@ fastlife/templates/pydantic_form/Dropdown.jinja,sha256=B3OlOOIY6trxfqEJZRUutWPbA
|
|
1669
1670
|
fastlife/templates/pydantic_form/Error.jinja,sha256=Wb5NnVRc4U7ZGKmYV7s4eGenWEug8WK9li48iTlX4cQ,121
|
1670
1671
|
fastlife/templates/pydantic_form/Hidden.jinja,sha256=NF8Od_mSSnRJAxWqsgeD1C5YzqJso-HCeDjGbpHjmlE,86
|
1671
1672
|
fastlife/templates/pydantic_form/Hint.jinja,sha256=O0ZsAQnATcG0a_qLQfrwM6VZHmAw3k1W33WYlEBUas8,123
|
1672
|
-
fastlife/templates/pydantic_form/Model.jinja,sha256=
|
1673
|
-
fastlife/templates/pydantic_form/Sequence.jinja,sha256=
|
1673
|
+
fastlife/templates/pydantic_form/Model.jinja,sha256=mZ7dAaDAdrvmEgL-Xq_GWgsd6rPDGhQvbu2XuoiS3K4,663
|
1674
|
+
fastlife/templates/pydantic_form/Sequence.jinja,sha256=0plVNnJ-E_lSv0izjJABqu-4ayP_XLBVRtqE_WVTISY,1443
|
1674
1675
|
fastlife/templates/pydantic_form/Text.jinja,sha256=7OJt4rC11-XCKFKDP8lIsFy6M33pRW9ceD9K-irhiJg,461
|
1676
|
+
fastlife/templates/pydantic_form/Textarea.jinja,sha256=1OP2NX8hv1scIQDypz2TcP0AL0CCbGE2uM5zYlty3n8,428
|
1675
1677
|
fastlife/templates/pydantic_form/Union.jinja,sha256=ibSgK04qE_GND8Fm0T5pBsP2LUaVe9lZQgSlVU6Cblk,1080
|
1676
|
-
fastlife/templates/pydantic_form/Widget.jinja,sha256=
|
1678
|
+
fastlife/templates/pydantic_form/Widget.jinja,sha256=S8RE4J0N5sNThJnnuuZPfy1YB0z2boSHfuEizyd8kKo,291
|
1677
1679
|
fastlife/templating/__init__.py,sha256=UY_hSTlJKDZnkSIK-BzRD4_AXOWgHbRuvJsKAS9ljgE,307
|
1678
1680
|
fastlife/templating/binding.py,sha256=noY9QrArJGqB1X80Ny-0zk1Dg6T9mMXahjEcIiHvioo,1648
|
1679
1681
|
fastlife/templating/renderer/__init__.py,sha256=6z7MTmj3-TgP_-cKtjhUypJcXvMOmaWaPHuoROyhobE,231
|
1680
|
-
fastlife/templating/renderer/abstract.py,sha256=
|
1682
|
+
fastlife/templating/renderer/abstract.py,sha256=9-y_1Ci4vyAggO9JUVlfN5dI9j7Uq1P4Mbp9bYFmTS8,3507
|
1681
1683
|
fastlife/templating/renderer/constants.py,sha256=A1DY6D5f32j7exAIgnuiLHbQHaq8BOcFt9xZHQHUBuU,6131
|
1682
|
-
fastlife/templating/renderer/jinjax.py,sha256=
|
1684
|
+
fastlife/templating/renderer/jinjax.py,sha256=hG-TOv92cB83OvyI9PI0PU2RZ3EkbMYE-ip7y9bMEB4,4030
|
1683
1685
|
fastlife/templating/renderer/widgets/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
1684
|
-
fastlife/templating/renderer/widgets/base.py,sha256=
|
1685
|
-
fastlife/templating/renderer/widgets/boolean.py,sha256
|
1686
|
-
fastlife/templating/renderer/widgets/checklist.py,sha256=
|
1687
|
-
fastlife/templating/renderer/widgets/dropdown.py,sha256=
|
1688
|
-
fastlife/templating/renderer/widgets/factory.py,sha256=
|
1689
|
-
fastlife/templating/renderer/widgets/hidden.py,sha256=
|
1690
|
-
fastlife/templating/renderer/widgets/model.py,sha256=
|
1691
|
-
fastlife/templating/renderer/widgets/sequence.py,sha256=
|
1692
|
-
fastlife/templating/renderer/widgets/text.py,sha256=
|
1693
|
-
fastlife/templating/renderer/widgets/union.py,sha256=
|
1686
|
+
fastlife/templating/renderer/widgets/base.py,sha256=O7fKA35WvkxOofLgSyoz8y2Dc8TBqIHPXKwL52-vui8,2926
|
1687
|
+
fastlife/templating/renderer/widgets/boolean.py,sha256=-pRzTjxGfdU1enewP0mm5Ppq979y0BTXtvhCecYQEmA,674
|
1688
|
+
fastlife/templating/renderer/widgets/checklist.py,sha256=1TsMX2PI4-ANjt0vFC1r-2GwWGpt78AC4UdOsVbbho4,1139
|
1689
|
+
fastlife/templating/renderer/widgets/dropdown.py,sha256=WO_YWI6A1eH6_ZMZfrrPumF5vRYD59h_dA6OTh0BzEg,1084
|
1690
|
+
fastlife/templating/renderer/widgets/factory.py,sha256=AOjqV82JzT4xOddHvCqPV5MeXE6ZuPlT6x-f9VyVmNc,17329
|
1691
|
+
fastlife/templating/renderer/widgets/hidden.py,sha256=0MgTIBPc7u3jrx9-I1PLaCQKfM2WTftVY7NbXVsBfeY,354
|
1692
|
+
fastlife/templating/renderer/widgets/model.py,sha256=ju3CCu_vtK29ffdiG-ODnA-T6mjdb9m6WRfdSqxtBTA,1275
|
1693
|
+
fastlife/templating/renderer/widgets/sequence.py,sha256=oEQ7KPMLeCzyteM1D_8U4kDjK_tEquvCpwygsaA4jj4,1515
|
1694
|
+
fastlife/templating/renderer/widgets/text.py,sha256=goM7tvkc1yzgalqzNNfsFjXv5Zlr9KzABXC0HFlO5kk,1637
|
1695
|
+
fastlife/templating/renderer/widgets/union.py,sha256=fj2HzVQsPpU9WaRrKDYsagFoEB7-KOnLn28q4GAZeuM,1933
|
1694
1696
|
fastlife/testing/__init__.py,sha256=vuqwoNUd3BuIp3fm7nkvmYkIGjIimf5zUGhDkeWrg2s,98
|
1695
1697
|
fastlife/testing/testclient.py,sha256=izNTkFgArIUrdSemNl3iiEDdsiUfnb2TtfKnZi3Jwv8,20546
|
1696
1698
|
fastlife/views/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
1697
1699
|
fastlife/views/pydantic_form.py,sha256=KJtH_DK8em0czGPsv0XpzGUFhtycyXdeRldwiU7d_j4,1257
|
1698
|
-
fastlifeweb-0.
|
1699
|
-
fastlifeweb-0.
|
1700
|
-
fastlifeweb-0.
|
1701
|
-
fastlifeweb-0.
|
1700
|
+
fastlifeweb-0.10.0.dist-info/LICENSE,sha256=F75xSseSKMwqzFj8rswYU6NWS3VoWOc_gY3fJYf9_LI,1504
|
1701
|
+
fastlifeweb-0.10.0.dist-info/METADATA,sha256=dfGVXkrcbzFCqCtE541S16toxUAn29MSho9nC7Kk1Ls,1886
|
1702
|
+
fastlifeweb-0.10.0.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
|
1703
|
+
fastlifeweb-0.10.0.dist-info/RECORD,,
|
File without changes
|
File without changes
|