fastlifeweb 0.9.7__py3-none-any.whl → 0.11.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/__init__.py +2 -2
- fastlife/config/__init__.py +13 -0
- fastlife/{configurator → config}/configurator.py +64 -41
- fastlife/{configurator → config}/registry.py +2 -10
- fastlife/{configurator → config}/settings.py +7 -3
- fastlife/middlewares/__init__.py +7 -0
- fastlife/middlewares/base.py +24 -0
- fastlife/middlewares/reverse_proxy/__init__.py +16 -0
- fastlife/middlewares/reverse_proxy/x_forwarded.py +1 -14
- fastlife/middlewares/session/__init__.py +16 -0
- fastlife/middlewares/session/middleware.py +6 -1
- fastlife/middlewares/session/serializer.py +21 -0
- fastlife/request/__init__.py +5 -0
- fastlife/request/{model_result.py → form.py} +21 -9
- fastlife/request/form_data.py +28 -3
- fastlife/request/request.py +18 -0
- fastlife/routing/__init__.py +7 -0
- fastlife/routing/route.py +45 -0
- fastlife/routing/router.py +12 -4
- fastlife/security/__init__.py +1 -0
- fastlife/security/csrf.py +29 -11
- fastlife/security/policy.py +6 -2
- fastlife/shared_utils/__init__.py +1 -0
- fastlife/shared_utils/infer.py +7 -0
- fastlife/shared_utils/resolver.py +10 -2
- fastlife/templates/A.jinja +33 -9
- fastlife/templates/Button.jinja +55 -32
- fastlife/templates/Checkbox.jinja +20 -6
- fastlife/templates/CsrfToken.jinja +4 -0
- fastlife/templates/Details.jinja +31 -3
- fastlife/templates/Form.jinja +45 -7
- fastlife/templates/H1.jinja +14 -1
- fastlife/templates/H2.jinja +14 -1
- fastlife/templates/H3.jinja +14 -1
- fastlife/templates/H4.jinja +14 -1
- fastlife/templates/H5.jinja +14 -1
- fastlife/templates/H6.jinja +14 -1
- fastlife/templates/Hidden.jinja +3 -3
- fastlife/templates/Input.jinja +21 -8
- fastlife/templates/Label.jinja +18 -2
- fastlife/templates/Option.jinja +14 -2
- fastlife/templates/P.jinja +14 -2
- fastlife/templates/Radio.jinja +34 -12
- fastlife/templates/Select.jinja +15 -4
- fastlife/templates/Summary.jinja +13 -2
- fastlife/templates/Table.jinja +12 -1
- fastlife/templates/Tbody.jinja +11 -1
- fastlife/templates/Td.jinja +12 -1
- fastlife/templates/Textarea.jinja +18 -0
- fastlife/templates/Tfoot.jinja +11 -1
- fastlife/templates/Th.jinja +12 -1
- fastlife/templates/Thead.jinja +11 -1
- fastlife/templates/Tr.jinja +11 -1
- fastlife/templates/pydantic_form/Boolean.jinja +3 -2
- fastlife/templates/pydantic_form/Checklist.jinja +3 -5
- fastlife/templates/pydantic_form/Dropdown.jinja +3 -2
- fastlife/templates/pydantic_form/Error.jinja +4 -3
- fastlife/templates/pydantic_form/Hidden.jinja +2 -1
- fastlife/templates/pydantic_form/Hint.jinja +2 -1
- fastlife/templates/pydantic_form/Model.jinja +16 -3
- fastlife/templates/pydantic_form/Sequence.jinja +15 -6
- fastlife/templates/pydantic_form/Text.jinja +2 -2
- fastlife/templates/pydantic_form/Textarea.jinja +32 -0
- fastlife/templates/pydantic_form/Union.jinja +7 -1
- fastlife/templates/pydantic_form/Widget.jinja +6 -3
- fastlife/templating/binding.py +18 -4
- fastlife/templating/renderer/__init__.py +3 -1
- fastlife/templating/renderer/abstract.py +21 -8
- fastlife/templating/renderer/constants.py +82 -0
- fastlife/templating/renderer/jinjax.py +269 -6
- fastlife/templating/renderer/widgets/base.py +43 -7
- fastlife/templating/renderer/widgets/boolean.py +21 -0
- fastlife/templating/renderer/widgets/checklist.py +23 -0
- fastlife/templating/renderer/widgets/dropdown.py +22 -2
- fastlife/templating/renderer/widgets/factory.py +100 -29
- fastlife/templating/renderer/widgets/hidden.py +16 -0
- fastlife/templating/renderer/widgets/model.py +7 -1
- fastlife/templating/renderer/widgets/sequence.py +8 -6
- fastlife/templating/renderer/widgets/text.py +80 -4
- fastlife/templating/renderer/widgets/union.py +25 -2
- fastlife/testing/testclient.py +3 -3
- fastlife/views/pydantic_form.py +2 -2
- {fastlifeweb-0.9.7.dist-info → fastlifeweb-0.11.0.dist-info}/METADATA +4 -9
- {fastlifeweb-0.9.7.dist-info → fastlifeweb-0.11.0.dist-info}/RECORD +86 -84
- fastlife/configurator/__init__.py +0 -4
- fastlife/configurator/base.py +0 -9
- fastlife/configurator/route_handler.py +0 -29
- fastlife/templates/__init__.py +0 -0
- {fastlifeweb-0.9.7.dist-info → fastlifeweb-0.11.0.dist-info}/LICENSE +0 -0
- {fastlifeweb-0.9.7.dist-info → fastlifeweb-0.11.0.dist-info}/WHEEL +0 -0
fastlife/templates/Label.jinja
CHANGED
@@ -1,2 +1,18 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
{# doc
|
2
|
+
Produce ``<label>`` node.
|
3
|
+
#}
|
4
|
+
{# def
|
5
|
+
for_: Annotated[str | None, "unique identifier of the target element."] = None,
|
6
|
+
id: Annotated[str | None, "unique identifier of the element."] = None,
|
7
|
+
class_: Annotated[
|
8
|
+
str | None,
|
9
|
+
"css class for the node, defaults to "
|
10
|
+
":attr:`fastlife.templating.renderer.constants.Constants.LABEL_CLASS`."
|
11
|
+
] = None,
|
12
|
+
#}
|
13
|
+
<label
|
14
|
+
{%- if attrs.for %} for="{{attrs.for}}" {%- endif %}
|
15
|
+
class="{{attrs.class or LABEL_CLASS}}"
|
16
|
+
{%- if id %} id="{{id}}" {%- endif %}>
|
17
|
+
{{-content -}}
|
18
|
+
</label>
|
fastlife/templates/Option.jinja
CHANGED
@@ -1,4 +1,16 @@
|
|
1
|
-
{#
|
2
|
-
|
1
|
+
{# doc
|
2
|
+
Produce ``<option>`` node.
|
3
|
+
#}
|
4
|
+
{# def
|
5
|
+
value: Annotated[str, "posted value after submitted the selected value"],
|
6
|
+
id: Annotated[str | None, "unique identifier of the element."] = None,
|
7
|
+
selected: Annotated[
|
8
|
+
Literal[True],
|
9
|
+
"Used to select the option while rendering the form"
|
10
|
+
] = False,
|
11
|
+
#}
|
12
|
+
<option value="{{value}}"
|
13
|
+
{%- if id %} id="{{id}}" {%- endif %}
|
14
|
+
{%- if selected %} selected {%- endif %}>
|
3
15
|
{{- content -}}
|
4
16
|
</option>
|
fastlife/templates/P.jinja
CHANGED
@@ -1,4 +1,16 @@
|
|
1
|
+
{# doc
|
2
|
+
Produce ``<p>`` node.
|
3
|
+
#}
|
1
4
|
{# def
|
2
|
-
id
|
5
|
+
id: Annotated[str | None, "unique identifier of the element."] = None,
|
6
|
+
class_: Annotated[
|
7
|
+
str | None,
|
8
|
+
"css class for the node, defaults to "
|
9
|
+
":attr:`fastlife.templating.renderer.constants.Constants.P_CLASS`."
|
10
|
+
] = None,
|
3
11
|
#}
|
4
|
-
<p
|
12
|
+
<p
|
13
|
+
{%- if id %} id="{{id}}" {% endif %}
|
14
|
+
class="{{ attrs.class or P_CLASS }}">
|
15
|
+
{{- content -}}
|
16
|
+
</p>
|
fastlife/templates/Radio.jinja
CHANGED
@@ -1,15 +1,37 @@
|
|
1
|
+
{# doc
|
2
|
+
Produce a ``<input type="radio">`` with its associated label inside a div.
|
3
|
+
#}
|
1
4
|
{# def
|
2
|
-
label,
|
3
|
-
name,
|
4
|
-
|
5
|
-
|
6
|
-
checked=
|
7
|
-
disabled=
|
8
|
-
onclick
|
5
|
+
label: Annotated[str, "label text associated to the radio"],
|
6
|
+
name: Annotated[str, "name of the submitted"],
|
7
|
+
value: Annotated[str, "value that will be submitted if selected"],
|
8
|
+
id: Annotated[str | None, "unique identifier of the element."],
|
9
|
+
checked: Annotated[bool, "Tick the radio button"] = False,
|
10
|
+
disabled: Annotated[bool, "Mark the radio button as disabled"] = False,
|
11
|
+
onclick: Annotated[str | None, "execute some javascript while clicking"] = None,
|
12
|
+
div_class: Annotated[
|
13
|
+
str | None,
|
14
|
+
"css class for the div node, defaults to "
|
15
|
+
":attr:`fastlife.templating.renderer.constants.Constants.RADIO_DIV_CLASS`"
|
16
|
+
] = None,
|
17
|
+
class_: Annotated[
|
18
|
+
str | None,
|
19
|
+
"css class for the input node, defaults to "
|
20
|
+
":attr:`fastlife.templating.renderer.constants.Constants.RADIO_INPUT_CLASS`"
|
21
|
+
] = None,
|
22
|
+
label_class: Annotated[
|
23
|
+
str | None,
|
24
|
+
"css class for the label node, defaults to "
|
25
|
+
":attr:`fastlife.templating.renderer.constants.Constants.RADIO_LABEL_CLASS`"
|
26
|
+
] = None,
|
9
27
|
#}
|
10
|
-
<div class="{{
|
11
|
-
<input type="radio" name="{{name}}" id="{{id}}" value="{{value}}"
|
12
|
-
class="{{attrs.class or RADIO_INPUT_CLASS
|
13
|
-
|
14
|
-
|
28
|
+
<div class="{{ div_class or RADIO_DIV_CLASS }}">
|
29
|
+
<input type="radio" name="{{ name }}" id="{{ id }}" value="{{ value }}"
|
30
|
+
class="{{ attrs.class or RADIO_INPUT_CLASS }}"
|
31
|
+
{%- if onclick %} onclick="{{onclick}}" {%- endif %}
|
32
|
+
{%- if checked %} checked {%- endif %}
|
33
|
+
{%- if disabled %} disabled {%- endif %}>
|
34
|
+
<Label :for="id" :class="label_class or RADIO_LABEL_CLASS">
|
35
|
+
{{- label -}}
|
36
|
+
</Label>
|
15
37
|
</div>
|
fastlife/templates/Select.jinja
CHANGED
@@ -1,9 +1,20 @@
|
|
1
|
+
{# doc
|
2
|
+
Create html ``<select>`` node.
|
3
|
+
#}
|
1
4
|
{# def
|
2
|
-
name,
|
3
|
-
id,
|
4
|
-
|
5
|
+
name: Annotated[str, "name of the submitted"],
|
6
|
+
id: Annotated[str | None, "unique identifier of the element."] = None,
|
7
|
+
class_: Annotated[
|
8
|
+
str | None,
|
9
|
+
"css class for the node, defaults to "
|
10
|
+
":attr:`fastlife.templating.renderer.constants.Constants.SELECT_CLASS`."
|
11
|
+
] = None,
|
12
|
+
multiple: Annotated[bool, "Mark as multiple"] = False,
|
5
13
|
#}
|
6
14
|
|
7
|
-
<select name="{{name}}"
|
15
|
+
<select name="{{name}}"
|
16
|
+
{%- if id %} id="{{ id }}" {%- endif %}
|
17
|
+
class="{{attrs.class or SELECT_CLASS}}"
|
18
|
+
{%- if multiple %} multiple {%- endif %}>
|
8
19
|
{{- content -}}
|
9
20
|
</select>
|
fastlife/templates/Summary.jinja
CHANGED
@@ -1,6 +1,17 @@
|
|
1
|
-
{#
|
1
|
+
{# doc
|
2
|
+
Create html ``<summary>`` node for the :jinjax:component:`Details` component.
|
3
|
+
#}
|
4
|
+
{# def
|
5
|
+
id: Annotated[str | None, "unique identifier of the element."] = None,
|
6
|
+
class_: Annotated[
|
7
|
+
str | None,
|
8
|
+
"css class for the node, defaults to "
|
9
|
+
":attr:`fastlife.templating.renderer.constants.Constants.SUMMARY_CLASS`."
|
10
|
+
] = None,
|
11
|
+
open: Annotated[bool, "Open or collapse the content of the details."] = True
|
12
|
+
#}
|
2
13
|
|
3
|
-
<summary id="{{id}}" class="
|
14
|
+
<summary id="{{id}}" :class="attrs.class or SUMMARY_CLASS"
|
4
15
|
style="list-style: none; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none;"
|
5
16
|
onclick="document.getElementById('{{id}}-icon').classList.toggle('rotate-90')">
|
6
17
|
<icons.ChevronRight :id="id + '-icon'"
|
fastlife/templates/Table.jinja
CHANGED
@@ -1,3 +1,14 @@
|
|
1
|
-
|
1
|
+
{# doc html ``<table>`` node. #}
|
2
|
+
{# def
|
3
|
+
id: Annotated[str | None, "unique identifier of the element."] = None,
|
4
|
+
class_: Annotated[
|
5
|
+
str | None,
|
6
|
+
"css class for the node, defaults to "
|
7
|
+
":attr:`fastlife.templating.renderer.constants.Constants.TABLE_CLASS`."
|
8
|
+
] = None,
|
9
|
+
#}
|
10
|
+
<table
|
11
|
+
{%- if id %} id="{{id}}" {%- endif %}
|
12
|
+
class="{{attrs.class or TABLE_CLASS}}">
|
2
13
|
{{- content -}}
|
3
14
|
</table>
|
fastlife/templates/Tbody.jinja
CHANGED
@@ -1,3 +1,13 @@
|
|
1
|
-
|
1
|
+
{# doc html ``<tbody>`` node. #}
|
2
|
+
{# def
|
3
|
+
id: Annotated[str | None, "unique identifier of the element."] = None,
|
4
|
+
class_: Annotated[
|
5
|
+
str | None,
|
6
|
+
"css class for the node."
|
7
|
+
] = None,
|
8
|
+
#}
|
9
|
+
<tbody
|
10
|
+
{%- if id %} id="{{id}}" {%- endif %}
|
11
|
+
{%- if class %} class="{{attrs.class}}" {%- endif %}>
|
2
12
|
{{- content -}}
|
3
13
|
</tbody>
|
fastlife/templates/Td.jinja
CHANGED
@@ -1,3 +1,14 @@
|
|
1
|
-
|
1
|
+
{# doc html ``<td>`` node. #}
|
2
|
+
{# def
|
3
|
+
id: Annotated[str | None, "unique identifier of the element."] = None,
|
4
|
+
class_: Annotated[
|
5
|
+
str | None,
|
6
|
+
"css class for the node, defaults to "
|
7
|
+
":attr:`fastlife.templating.renderer.constants.Constants.TD_CLASS`"
|
8
|
+
] = None,
|
9
|
+
#}
|
10
|
+
<td
|
11
|
+
{%- if id %} id="{{id}}" {%- endif %}
|
12
|
+
class="{{attrs.class or TD_CLASS}}">
|
2
13
|
{{- content -}}
|
3
14
|
</td>
|
@@ -0,0 +1,18 @@
|
|
1
|
+
{# doc html ``<textarea>`` node. #}
|
2
|
+
{# def
|
3
|
+
name: Annotated[str, "name of the submitted"],
|
4
|
+
id: Annotated[str | None, "unique identifier of the element."],
|
5
|
+
aria_label: Annotated[str | None, "aria-label"] = None,
|
6
|
+
placeholder: Annotated[
|
7
|
+
str | None,
|
8
|
+
"brief hint to the user as to what kind of information is expected in the field"
|
9
|
+
] = None,
|
10
|
+
#}
|
11
|
+
|
12
|
+
<textarea name="{{ name }}"
|
13
|
+
{%- if id %} id="{{ id }}" {%- endif %}
|
14
|
+
{%- if aria_label %} aria-label="{{ aria_label }}" {%- endif %}
|
15
|
+
{%- if placeholder %} placeholder="{{ placeholder }}" {%- endif %}
|
16
|
+
class="{{attrs.class or INPUT_CLASS}}">
|
17
|
+
{{- content -}}
|
18
|
+
</textarea>
|
fastlife/templates/Tfoot.jinja
CHANGED
@@ -1,3 +1,13 @@
|
|
1
|
-
|
1
|
+
{# doc html ``<tfoot>`` node. #}
|
2
|
+
{# def
|
3
|
+
id: Annotated[str | None, "unique identifier of the element."] = None,
|
4
|
+
class_: Annotated[
|
5
|
+
str | None,
|
6
|
+
"css class for the node."
|
7
|
+
] = None,
|
8
|
+
#}
|
9
|
+
<tfoot
|
10
|
+
{%- if id %} id="{{id}}" {%- endif %}
|
11
|
+
{%- if class %} class="{{attrs.class}}" {%- endif %}>
|
2
12
|
{{- content -}}
|
3
13
|
</tfoot>
|
fastlife/templates/Th.jinja
CHANGED
@@ -1,3 +1,14 @@
|
|
1
|
-
|
1
|
+
{# doc html ``<th>`` node. #}
|
2
|
+
{# def
|
3
|
+
id: Annotated[str | None, "unique identifier of the element."] = None,
|
4
|
+
class_: Annotated[
|
5
|
+
str | None,
|
6
|
+
"css class for the node, defaults to "
|
7
|
+
":attr:`fastlife.templating.renderer.constants.Constants.TH_CLASS`."
|
8
|
+
] = None,
|
9
|
+
#}
|
10
|
+
<th
|
11
|
+
{%- if id %} id="{{id}}" {%- endif %}
|
12
|
+
class="{{attrs.class or TH_CLASS}}">
|
2
13
|
{{- content -}}
|
3
14
|
</th>
|
fastlife/templates/Thead.jinja
CHANGED
@@ -1,3 +1,13 @@
|
|
1
|
-
|
1
|
+
{# doc html ``<thead>`` node. #}
|
2
|
+
{# def
|
3
|
+
id: Annotated[str | None, "unique identifier of the element."] = None,
|
4
|
+
class_: Annotated[
|
5
|
+
str | None,
|
6
|
+
"css class for the node"
|
7
|
+
] = None,
|
8
|
+
#}
|
9
|
+
<thead
|
10
|
+
{%- if id %} id="{{id}}" {%- endif %}
|
11
|
+
{%- if class %} class="{{attrs.class}}" {%- endif %}>
|
2
12
|
{{- content -}}
|
3
13
|
</thead>
|
fastlife/templates/Tr.jinja
CHANGED
@@ -1,3 +1,13 @@
|
|
1
|
-
|
1
|
+
{# doc html ``<tr>`` node. #}
|
2
|
+
{# def
|
3
|
+
id: Annotated[str | None, "unique identifier of the element."] = None,
|
4
|
+
class_: Annotated[
|
5
|
+
str | None,
|
6
|
+
"css class for the node."
|
7
|
+
] = None,
|
8
|
+
#}
|
9
|
+
<tr
|
10
|
+
{%- if id %} id="{{id}}" {%- endif %}
|
11
|
+
{%- if class %} class="{{attrs.class}}" {%- endif %}>
|
2
12
|
{{- content -}}
|
3
13
|
</tr>
|
@@ -1,5 +1,6 @@
|
|
1
|
-
{#
|
2
|
-
|
1
|
+
{# doc Render a checkbox and a label for boolean field. #}
|
2
|
+
{# def widget: Annotated[fastlife.templating.renderer.widgets.boolean.BooleanWidget, "widget to display."] #}
|
3
|
+
<pydantic_form.Widget :widget="widget">
|
3
4
|
<div class="pt-4">
|
4
5
|
<div class="flex items-center">
|
5
6
|
<Checkbox :name="widget.name" type="checkbox" :id="widget.id" :checked="widget.value" value="1" />
|
@@ -1,8 +1,6 @@
|
|
1
|
-
{#
|
2
|
-
widget,
|
3
|
-
|
4
|
-
|
5
|
-
<pydantic_form.Widget :widget="widget" :removable="widget.removable">
|
1
|
+
{# doc Render a list of checkbox and a label for Literal and enum fields #}
|
2
|
+
{# def widget: Annotated[fastlife.templating.renderer.widgets.checklist.ChecklistWidget, "widget to display."] #}
|
3
|
+
<pydantic_form.Widget :widget="widget">
|
6
4
|
<div class="pt-4">
|
7
5
|
<Details>
|
8
6
|
<Summary :id="widget.id + '-summary'">
|
@@ -1,6 +1,7 @@
|
|
1
|
-
{#
|
1
|
+
{# doc Render a Select with options #}
|
2
|
+
{# def widget: Annotated[fastlife.templating.renderer.widgets.dropdown.DropDownWidget, "widget to display."] #}
|
2
3
|
|
3
|
-
<pydantic_form.Widget :widget="widget"
|
4
|
+
<pydantic_form.Widget :widget="widget">
|
4
5
|
<div class="pt-4">
|
5
6
|
<Label :for="widget.id">{{widget.title}}</Label>
|
6
7
|
<Select :name="widget.name" :id="widget.id">
|
@@ -1,7 +1,15 @@
|
|
1
|
-
{#
|
1
|
+
{# doc Widget for pydantic BaseModel subclasses. #}
|
2
|
+
{# def
|
3
|
+
widget: Annotated[fastlife.templating.renderer.widgets.model.ModelWidget, "widget to display."],
|
4
|
+
children_widget : Annotated[
|
5
|
+
Sequence[fastlife.templating.renderer.widgets.base.Widget],
|
6
|
+
"child widgets for every fields of the model."
|
7
|
+
],
|
8
|
+
#}
|
2
9
|
|
3
|
-
<pydantic_form.Widget :widget="widget"
|
4
|
-
<div id="{{widget.id}}" class="m-4">
|
10
|
+
<pydantic_form.Widget :widget="widget">
|
11
|
+
<div id="{{widget.id}}"{% if widget.nested %} class="m-4"{%endif%}>
|
12
|
+
{% if widget.nested %}
|
5
13
|
<Details>
|
6
14
|
<Summary :id="widget.id + '-summary'">
|
7
15
|
<H3 :class="H3_SUMMARY_CLASS">{{widget.title}}</H3>
|
@@ -13,5 +21,10 @@
|
|
13
21
|
{% endfor %}
|
14
22
|
</div>
|
15
23
|
</Details>
|
24
|
+
{% else %}
|
25
|
+
{% for child in children_widget %}
|
26
|
+
{{ child }}
|
27
|
+
{% endfor %}
|
28
|
+
{% endif %}
|
16
29
|
</div>
|
17
30
|
</pydantic_form.Widget>
|
@@ -1,6 +1,14 @@
|
|
1
|
-
{#
|
1
|
+
{# doc Widget for pydantic BaseModel subclasses. #}
|
2
|
+
{# def
|
3
|
+
widget: Annotated[fastlife.templating.renderer.widgets.sequence.SequenceWidget, "widget to display."],
|
4
|
+
children_widgets : Annotated[
|
5
|
+
Sequence[fastlife.templating.renderer.widgets.base.Widget],
|
6
|
+
"child widgets for every fields of the model."
|
7
|
+
],
|
8
|
+
type: Annotated[fastlife.templating.renderer.widgets.base.TypeWrapper, "child type wrapped."]
|
9
|
+
#}
|
2
10
|
|
3
|
-
<pydantic_form.Widget :widget="widget"
|
11
|
+
<pydantic_form.Widget :widget="widget">
|
4
12
|
<Details :id="widget.id">
|
5
13
|
<Summary :id="widget.id + '-summary'">
|
6
14
|
<H3 :class="H3_SUMMARY_CLASS">{{widget.title}}</H3>
|
@@ -18,15 +26,16 @@
|
|
18
26
|
</script>
|
19
27
|
|
20
28
|
<div id="{{widget.id}}-content" class="m-4" data-length="{{children_widgets|length|string}}">
|
21
|
-
{%
|
22
|
-
{% set container_id = widget.id + "-container" %}
|
29
|
+
{% set container_id = widget.id + "-children-container" %}
|
23
30
|
<div id="{{container_id}}">
|
31
|
+
{% for child in children_widgets %}
|
24
32
|
{{ child }}
|
33
|
+
{% endfor%}
|
25
34
|
</div>
|
26
|
-
{% endfor%}
|
27
35
|
</div>
|
36
|
+
|
28
37
|
<div>
|
29
|
-
{% set container_id = "#" + widget.id + "-container" %}
|
38
|
+
{% set container_id = "#" + widget.id + "-children-container" %}
|
30
39
|
{% set add_id = widget.id + "-add" %}
|
31
40
|
{% set vals = 'js:{"name": '+ fnGetName + '(), "token": "' + type.token + '", "removable": true}' %}
|
32
41
|
<Button type="button" :hx-target="container_id" hx-swap="beforeend" :id="add_id" :hx-vals="vals" :hx-get="type.url">
|
@@ -1,6 +1,6 @@
|
|
1
|
-
{# def widget #}
|
1
|
+
{# def widget: Annotated[fastlife.templating.renderer.widgets.text.TextWidget, "widget to display."] #}
|
2
2
|
|
3
|
-
<pydantic_form.Widget :widget="widget"
|
3
|
+
<pydantic_form.Widget :widget="widget">
|
4
4
|
<div class="pt-4">
|
5
5
|
<Label :for="widget.id">{{widget.title}}</Label>
|
6
6
|
<pydantic_form.Error :text="widget.error" />
|
@@ -0,0 +1,32 @@
|
|
1
|
+
{# doc
|
2
|
+
Render textarea widget for field of type text of event sequence.
|
3
|
+
|
4
|
+
::
|
5
|
+
|
6
|
+
from fastlife.templating.renderer.widgets.text import TextareaWidget
|
7
|
+
from pydantic import BaseModel, Field, field_validator
|
8
|
+
|
9
|
+
class TagsForm(BaseModel):
|
10
|
+
|
11
|
+
tags: Annotated[Sequence[str], TextareaWidget] = Field(
|
12
|
+
default_factory=list,
|
13
|
+
title="Tags",
|
14
|
+
description="One tag per line",
|
15
|
+
)
|
16
|
+
|
17
|
+
@field_validator("tags", mode="before")
|
18
|
+
def split(cls, s: Any) -> Sequence[str]:
|
19
|
+
return s.split() if s else []
|
20
|
+
|
21
|
+
#}
|
22
|
+
{# def widget: Annotated[fastlife.templating.renderer.widgets.text.TextareaWidget, "widget to display."] #}
|
23
|
+
|
24
|
+
<pydantic_form.Widget :widget="widget">
|
25
|
+
<div class="pt-4">
|
26
|
+
<Label :for="widget.id">{{widget.title}}</Label>
|
27
|
+
<pydantic_form.Error :text="widget.error" />
|
28
|
+
<Textarea :name="widget.name" :id="widget.id" :aria-label="widget.aria_label">{% for v in widget.value%}{{v}}
|
29
|
+
{% endfor %}</Textarea>
|
30
|
+
<pydantic_form.Hint :text="widget.hint" />
|
31
|
+
</div>
|
32
|
+
</pydantic_form.Widget>
|
@@ -1,4 +1,10 @@
|
|
1
|
-
{#
|
1
|
+
{# doc display widget for union type field #}
|
2
|
+
{# def
|
3
|
+
widget: Annotated[fastlife.templating.renderer.widgets.base.Widget, "widget to display."],
|
4
|
+
child: Annotated[fastlife.templating.renderer.widgets.base.Widget, "current widget if any"],
|
5
|
+
types: Annotated[Sequence[fastlife.templating.renderer.widgets.base.TypeWrapper], "Child types to choose"],
|
6
|
+
parent_type: Annotated[fastlife.templating.renderer.widgets.base.TypeWrapper, "parent type"]
|
7
|
+
#}
|
2
8
|
|
3
9
|
<pydantic_form.Widget :widget="widget">
|
4
10
|
<div id="{{widget.id}}">
|
@@ -1,9 +1,12 @@
|
|
1
|
-
{#
|
1
|
+
{# doc Base component for widget #}
|
2
|
+
{# def
|
3
|
+
widget: Annotated[fastlife.templating.renderer.widgets.base.Widget, "widget to display."],
|
4
|
+
#}
|
2
5
|
{% set container_id = widget.id + "-container" %}
|
3
6
|
<div id="{{container_id}}">
|
4
7
|
{{ content }}
|
5
|
-
{% if removable %}
|
6
|
-
<Button type="button" :onclick={{"document.getElementById('" + container_id + "').remove()"}}>
|
8
|
+
{% if widget.removable %}
|
9
|
+
<Button type="button" :onclick={{"document.getElementById('" + container_id + "').remove()" }}>
|
7
10
|
Remove
|
8
11
|
</Button>
|
9
12
|
{% endif %}
|
fastlife/templating/binding.py
CHANGED
@@ -1,21 +1,35 @@
|
|
1
|
+
"""
|
2
|
+
Bind template to the view in order to build an html response.
|
3
|
+
"""
|
4
|
+
|
1
5
|
from typing import Any, Callable
|
2
6
|
|
3
|
-
from fastapi import Depends,
|
7
|
+
from fastapi import Depends, Response
|
4
8
|
|
5
|
-
from fastlife.
|
9
|
+
from fastlife.request import Request
|
6
10
|
from fastlife.security.csrf import create_csrf_token
|
7
11
|
|
8
12
|
Template = Callable[..., Response]
|
9
|
-
|
13
|
+
"""Type to annotate a FastAPI depency injection."""
|
14
|
+
|
15
|
+
TemplateEngine = Callable[[Request], Template]
|
10
16
|
|
11
17
|
|
12
18
|
def get_template(template: str, *, content_type: str = "text/html") -> TemplateEngine:
|
19
|
+
"""
|
20
|
+
Return a closure to render the given template.
|
21
|
+
|
22
|
+
:param template: path to template to render.
|
23
|
+
:param content_type: response ``Content-Type`` header.
|
24
|
+
"""
|
25
|
+
|
13
26
|
def render_template(
|
14
|
-
reg: "Registry",
|
15
27
|
request: Request,
|
16
28
|
*,
|
17
29
|
_create_csrf_token: Callable[..., str] = create_csrf_token,
|
18
30
|
) -> Template:
|
31
|
+
reg = request.registry
|
32
|
+
|
19
33
|
def parametrizer(**kwargs: Any) -> Response:
|
20
34
|
request.scope[reg.settings.csrf_token_name] = (
|
21
35
|
request.cookies.get(reg.settings.csrf_token_name)
|
@@ -1,9 +1,11 @@
|
|
1
|
+
"""Template renderer."""
|
2
|
+
|
1
3
|
from .abstract import AbstractTemplateRendererFactory
|
2
4
|
from .constants import Constants
|
3
5
|
from .jinjax import JinjaxTemplateRenderer
|
4
6
|
|
5
7
|
__all__ = [
|
6
8
|
"AbstractTemplateRendererFactory",
|
7
|
-
"Constants",
|
8
9
|
"JinjaxTemplateRenderer",
|
10
|
+
"Constants",
|
9
11
|
]
|
@@ -1,3 +1,13 @@
|
|
1
|
+
"""
|
2
|
+
Base class to of the template renderer.
|
3
|
+
|
4
|
+
Fastlife comes with :class:`fastlife.templating.renderer.jinjax.JinjaxTemplateRenderer`,
|
5
|
+
the rendering engine, it can be overriden from the setting
|
6
|
+
:attr:`fastlife.config.settings.Settings.template_renderer_class`.
|
7
|
+
|
8
|
+
In that case, those base classes have to be implemented.
|
9
|
+
|
10
|
+
"""
|
1
11
|
import abc
|
2
12
|
from typing import Any, Mapping, Optional, Type
|
3
13
|
|
@@ -5,7 +15,7 @@ from fastapi import Request
|
|
5
15
|
from markupsafe import Markup
|
6
16
|
from pydantic.fields import FieldInfo
|
7
17
|
|
8
|
-
from fastlife.request.
|
18
|
+
from fastlife.request.form import FormModel
|
9
19
|
|
10
20
|
|
11
21
|
class AbstractTemplateRenderer(abc.ABC):
|
@@ -15,7 +25,7 @@ class AbstractTemplateRenderer(abc.ABC):
|
|
15
25
|
"""
|
16
26
|
|
17
27
|
route_prefix: str
|
18
|
-
"""Used to buid pydantic form"""
|
28
|
+
"""Used to buid pydantic form."""
|
19
29
|
|
20
30
|
@abc.abstractmethod
|
21
31
|
def render_template(
|
@@ -38,7 +48,7 @@ class AbstractTemplateRenderer(abc.ABC):
|
|
38
48
|
to every templates. This can be used to fillout options in a select without
|
39
49
|
performing an ajax request for example.
|
40
50
|
|
41
|
-
:param template: name of the template to render
|
51
|
+
:param template: name of the template to render.
|
42
52
|
:param globals: some variable that will be passed to all rendered templates.
|
43
53
|
:param params: paramaters that are limited to the main rendered templates.
|
44
54
|
:return: The template rendering result.
|
@@ -46,7 +56,7 @@ class AbstractTemplateRenderer(abc.ABC):
|
|
46
56
|
|
47
57
|
@abc.abstractmethod
|
48
58
|
def pydantic_form(
|
49
|
-
self, model:
|
59
|
+
self, model: FormModel[Any], *, token: Optional[str] = None
|
50
60
|
) -> Markup:
|
51
61
|
"""
|
52
62
|
Render an http form from a given model.
|
@@ -57,16 +67,16 @@ class AbstractTemplateRenderer(abc.ABC):
|
|
57
67
|
|
58
68
|
this function is used inside the template directly. And it will not render the
|
59
69
|
<form> tag so the action/httpx post is not handled byu the method..
|
60
|
-
Somethinging like this
|
70
|
+
Somethinging like this:
|
61
71
|
|
62
72
|
::
|
63
73
|
|
64
|
-
<
|
74
|
+
<Form action="" method="post">
|
65
75
|
{{ pydantic_form(model) }}
|
66
|
-
</
|
76
|
+
</Form>
|
67
77
|
|
68
78
|
|
69
|
-
:param model: model to render
|
79
|
+
:param model: model to render.
|
70
80
|
:param token: a random string that can be passed for testing purpose.
|
71
81
|
"""
|
72
82
|
...
|
@@ -94,6 +104,9 @@ class AbstractTemplateRenderer(abc.ABC):
|
|
94
104
|
class AbstractTemplateRendererFactory(abc.ABC):
|
95
105
|
"""
|
96
106
|
The template render factory.
|
107
|
+
|
108
|
+
The implementation of this class is found using the settings
|
109
|
+
:attr:`fastlife.config.settings.Settings.template_renderer_class`.
|
97
110
|
"""
|
98
111
|
|
99
112
|
@abc.abstractmethod
|