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
@@ -0,0 +1,45 @@
|
|
1
|
+
"""HTTP Route."""
|
2
|
+
from typing import TYPE_CHECKING, Any, Callable, Coroutine
|
3
|
+
|
4
|
+
from fastapi.routing import APIRoute
|
5
|
+
from starlette.requests import Request as StarletteRequest
|
6
|
+
from starlette.responses import Response
|
7
|
+
|
8
|
+
from fastlife.request.request import Request
|
9
|
+
|
10
|
+
if TYPE_CHECKING:
|
11
|
+
from fastlife.config.registry import AppRegistry # coverage: ignore
|
12
|
+
|
13
|
+
|
14
|
+
class Route(APIRoute):
|
15
|
+
"""
|
16
|
+
Routing for fastlife application.
|
17
|
+
|
18
|
+
The fastlife router construct fastlife request object in order to
|
19
|
+
have the registry property available in every received request.
|
20
|
+
"""
|
21
|
+
|
22
|
+
_registry: "AppRegistry"
|
23
|
+
"""
|
24
|
+
The application registry.
|
25
|
+
|
26
|
+
this static variable is initialized by the configurator during
|
27
|
+
the startup and keep the registry during the lifetime of the application.
|
28
|
+
|
29
|
+
this variable should be accessed via the request object or the
|
30
|
+
:class:`fastlife.config.Registry` depenency injection.
|
31
|
+
"""
|
32
|
+
|
33
|
+
def get_route_handler(
|
34
|
+
self,
|
35
|
+
) -> Callable[[StarletteRequest], Coroutine[Any, Any, Response]]:
|
36
|
+
"""
|
37
|
+
Replace the request object by the fastlife request associated with the registry.
|
38
|
+
"""
|
39
|
+
orig_route_handler = super().get_route_handler()
|
40
|
+
|
41
|
+
async def route_handler(request: StarletteRequest) -> Response:
|
42
|
+
req = Request(self._registry, request)
|
43
|
+
return await orig_route_handler(req)
|
44
|
+
|
45
|
+
return route_handler
|
fastlife/routing/router.py
CHANGED
@@ -1,13 +1,21 @@
|
|
1
|
+
"""
|
2
|
+
FastApi router for fastlife application.
|
3
|
+
|
4
|
+
The aim of this router is get :class:`fastlife.routing.route.Route`
|
5
|
+
available in the FastApi request depency injection.
|
6
|
+
"""
|
1
7
|
from typing import Any
|
2
8
|
|
3
9
|
from fastapi import APIRouter
|
4
10
|
|
5
|
-
from fastlife.
|
11
|
+
from fastlife.routing.route import Route
|
6
12
|
|
7
13
|
|
8
|
-
class
|
9
|
-
"""
|
14
|
+
class Router(APIRouter):
|
15
|
+
"""
|
16
|
+
The router used split your app in many routes.
|
17
|
+
"""
|
10
18
|
|
11
19
|
def __init__(self, **kwargs: Any) -> None:
|
12
|
-
kwargs["route_class"] =
|
20
|
+
kwargs["route_class"] = Route
|
13
21
|
super().__init__(**kwargs)
|
fastlife/security/__init__.py
CHANGED
@@ -0,0 +1 @@
|
|
1
|
+
"""Security features."""
|
fastlife/security/csrf.py
CHANGED
@@ -1,15 +1,27 @@
|
|
1
1
|
"""
|
2
|
-
Prevents CSRF attack using cookie and html hidden field
|
2
|
+
Prevents CSRF attack using cookie and html hidden field comparison.
|
3
|
+
|
4
|
+
Fast life did not reinvent the wheel on CSRF Protection.
|
5
|
+
|
6
|
+
It use the good old method. A CSRF token is saved in a cookie.
|
7
|
+
Forms post the CSRF token, and the token in the cookies and the form must match
|
8
|
+
to process the request, otherwise an exception
|
9
|
+
:class:`fastlife.security.csrf.CSRFAttack` is raised.
|
10
|
+
|
11
|
+
The cookie named is configurabllefia the settings
|
12
|
+
:attr:`fastlife.config.settings.Settings.csrf_token_name`
|
13
|
+
|
14
|
+
While using the `<Form/>` JinjaX tag, the csrf token is always sent.
|
15
|
+
|
16
|
+
The cookie is always set when you render any template. At the moment, there is
|
17
|
+
no way to prevent to set the cookie in the request.
|
3
18
|
|
4
|
-
Fast life did not reinvent the wheel on CSRF Protection. It use the good old method.
|
5
19
|
"""
|
6
|
-
import secrets
|
7
|
-
from typing import TYPE_CHECKING, Any, Callable, Coroutine
|
8
20
|
|
9
|
-
|
21
|
+
import secrets
|
22
|
+
from typing import Any, Callable, Coroutine
|
10
23
|
|
11
|
-
|
12
|
-
from fastlife.configurator.registry import Registry # coverage: ignore
|
24
|
+
from fastlife.request import Request
|
13
25
|
|
14
26
|
|
15
27
|
class CSRFAttack(Exception):
|
@@ -23,12 +35,17 @@ def create_csrf_token() -> str:
|
|
23
35
|
return secrets.token_urlsafe(5)
|
24
36
|
|
25
37
|
|
26
|
-
def check_csrf(
|
38
|
+
def check_csrf() -> Callable[[Request], Coroutine[Any, Any, bool]]:
|
27
39
|
"""
|
28
40
|
A global application dependency, that is always active.
|
29
41
|
|
30
|
-
If you don't want csrf token, its simple don't use the
|
42
|
+
If you don't want csrf token, its simple: don't use the
|
31
43
|
application/x-www-form-urlencoded on a POST method.
|
44
|
+
|
45
|
+
For security reason, there it no other options to disable this policy.
|
46
|
+
|
47
|
+
:raises: {class}`.CSRFAttack` if the cookie and the csrf
|
48
|
+
posted in the form does not match.
|
32
49
|
"""
|
33
50
|
|
34
51
|
async def check_csrf(request: Request) -> bool:
|
@@ -38,13 +55,14 @@ def check_csrf(registry: "Registry") -> Callable[[Request], Coroutine[Any, Any,
|
|
38
55
|
!= "application/x-www-form-urlencoded"
|
39
56
|
):
|
40
57
|
return True
|
58
|
+
csrf_token_name = request.registry.settings.csrf_token_name
|
41
59
|
|
42
|
-
cookie = request.cookies.get(
|
60
|
+
cookie = request.cookies.get(csrf_token_name)
|
43
61
|
if not cookie:
|
44
62
|
raise CSRFAttack("CSRF token did not match")
|
45
63
|
|
46
64
|
form_data = await request.form()
|
47
|
-
value = form_data.get(
|
65
|
+
value = form_data.get(csrf_token_name)
|
48
66
|
if value != cookie:
|
49
67
|
raise CSRFAttack("CSRF token did not match")
|
50
68
|
|
fastlife/security/policy.py
CHANGED
@@ -9,8 +9,12 @@ def check_permission(permission_name: str) -> CheckPermissionHook:
|
|
9
9
|
"""
|
10
10
|
A closure that check that a user as the given permission_name.
|
11
11
|
|
12
|
-
This method has to be overriden using the setting
|
13
|
-
to implement it.
|
12
|
+
This method has to be overriden using the setting
|
13
|
+
:attr:`fastlife.config.settings.Settings.check_permission` to implement it.
|
14
|
+
|
15
|
+
When the check permission is properly set in the settings., the hook is called
|
16
|
+
for every route added with a permission keyword.
|
17
|
+
:meth:`fastlife.config.configurator.Configurator.add_route`
|
14
18
|
|
15
19
|
:param permission_name: a permission name set in a view to check access.
|
16
20
|
:return: a function that raise http exceptions or any configured exception here.
|
@@ -0,0 +1 @@
|
|
1
|
+
"""Utilities function."""
|
fastlife/shared_utils/infer.py
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
"""Type inference."""
|
1
2
|
from types import UnionType
|
2
3
|
from typing import Any, Type, Union, get_origin
|
3
4
|
|
@@ -5,10 +6,16 @@ from pydantic import BaseModel
|
|
5
6
|
|
6
7
|
|
7
8
|
def is_complex_type(typ: Type[Any]) -> bool:
|
9
|
+
"""
|
10
|
+
Used to detect complex type such as Mapping, Sequence and pydantic BaseModel.
|
11
|
+
|
12
|
+
This method cannot be used outside pydantic serialization.
|
13
|
+
"""
|
8
14
|
return bool(get_origin(typ) or issubclass(typ, BaseModel))
|
9
15
|
|
10
16
|
|
11
17
|
def is_union(typ: Type[Any]) -> bool:
|
18
|
+
"""Used to detect unions like Optional[T], Union[T, U] or T | U."""
|
12
19
|
type_origin = get_origin(typ)
|
13
20
|
if type_origin:
|
14
21
|
if type_origin is Union: # Optional[T]
|
@@ -1,6 +1,8 @@
|
|
1
|
+
"""Resolution of python objects for dependency injection and more."""
|
1
2
|
import importlib.util
|
2
3
|
from pathlib import Path
|
3
|
-
from
|
4
|
+
from types import UnionType
|
5
|
+
from typing import Any, Union
|
4
6
|
|
5
7
|
|
6
8
|
def resolve(value: str) -> Any:
|
@@ -24,7 +26,8 @@ def resolve(value: str) -> Any:
|
|
24
26
|
return attr
|
25
27
|
|
26
28
|
|
27
|
-
def resolve_extended(value: str) ->
|
29
|
+
def resolve_extended(value: str) -> UnionType:
|
30
|
+
"""Resolve many types separed by a pipe (``|``), the union separator."""
|
28
31
|
values = value.split("|")
|
29
32
|
if len(values) == 1:
|
30
33
|
return resolve(value)
|
@@ -33,6 +36,11 @@ def resolve_extended(value: str) -> Type[Any]:
|
|
33
36
|
|
34
37
|
|
35
38
|
def resolve_path(value: str) -> str:
|
39
|
+
"""
|
40
|
+
Resole a path on the disk from a python package name.
|
41
|
+
|
42
|
+
This helper is used to find static assets inside a python package.
|
43
|
+
"""
|
36
44
|
package_name, resource_name = value.split(":", 1)
|
37
45
|
spec = importlib.util.find_spec(package_name)
|
38
46
|
if not spec or not spec.origin:
|
fastlife/templates/A.jinja
CHANGED
@@ -1,14 +1,38 @@
|
|
1
|
+
{# doc
|
2
|
+
Create html ``<a>`` node with htmx support by default.
|
3
|
+
The `hx-get` parameter is set with the href directly unless the
|
4
|
+
`disabled-htmx` attribute has been set.
|
5
|
+
#}
|
1
6
|
{# def
|
2
|
-
href,
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
7
|
+
href: Annotated[str, "target link."],
|
8
|
+
id: Annotated[str | None, "unique identifier of the element."] = None,
|
9
|
+
class_: Annotated[
|
10
|
+
str | None,
|
11
|
+
"css class for the node, defaults to :attr:`fastlife.templating.renderer.constants.Constants.A_CLASS`."
|
12
|
+
] = None,
|
13
|
+
hx_target: Annotated[
|
14
|
+
str,
|
15
|
+
"target the element for swapping than the one issuing the AJAX request."
|
16
|
+
] = "#maincontent",
|
17
|
+
hx_select: Annotated[str | None, "select the content swapped from response of the AJAX request."] = None,
|
18
|
+
hx_swap: Annotated[
|
19
|
+
str,
|
20
|
+
"specify how the response will be swapped in relative to the target of an AJAX request."
|
21
|
+
] = "innerHTML show:body:top",
|
22
|
+
hx_push_url: Annotated[bool, "replace the browser url with the link."] = True,
|
23
|
+
disable_htmx: Annotated[bool, "do not add any `hx-*` attibute to the link."] = False
|
8
24
|
#}
|
9
25
|
|
10
|
-
<a href="{{href}}"
|
11
|
-
|
12
|
-
|
26
|
+
<a href="{{href}}"
|
27
|
+
{%- if id %} id="{{ id }}" {%- endif %}
|
28
|
+
{%- if not disable_htmx %}
|
29
|
+
hx-get="{{ href }}"
|
30
|
+
hx-target="{{ hx_target }}"
|
31
|
+
hx-swap="{{ hx_swap }}"
|
32
|
+
{%- if hx_push_url %} hx-push-url="true" {%- endif %}
|
33
|
+
{%- if hx_select %} hx-select="{{ hx_select }}" {%- endif %}
|
34
|
+
{%- endif %}
|
35
|
+
class="{{ attrs.class or A_CLASS }}"
|
36
|
+
>
|
13
37
|
{{- content -}}
|
14
38
|
</a>
|
fastlife/templates/Button.jinja
CHANGED
@@ -1,35 +1,58 @@
|
|
1
|
+
{# doc
|
2
|
+
Create html ``<button>`` node.
|
3
|
+
#}
|
1
4
|
{# def
|
2
|
-
type
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
5
|
+
type: Annotated[
|
6
|
+
Literal["submit", "button", "reset"],
|
7
|
+
"Define button behavior."
|
8
|
+
] = "submit",
|
9
|
+
id: Annotated[str | None, "unique identifier of the element."] = None,
|
10
|
+
class_: Annotated[
|
11
|
+
str | None,
|
12
|
+
"css class for the node, defaults to "
|
13
|
+
":attr:`fastlife.templating.renderer.constants.Constants.BUTTON_CLASS`."
|
14
|
+
] = None,
|
15
|
+
name: str = "action",
|
16
|
+
value: str = "submit",
|
17
|
+
hidden: bool = False,
|
18
|
+
aria_label: str | None = None,
|
19
|
+
onclick: str | None = None,
|
20
|
+
hx_target: str | None = None,
|
21
|
+
hx_swap: str | None = None,
|
22
|
+
hx_select: str | None = None,
|
23
|
+
hx_after_request: Annotated[str, "Produce the hx-on::after-request"] = "",
|
24
|
+
hx_vals: str | None = None,
|
25
|
+
hx_confirm: str | None = None,
|
26
|
+
hx_get: str | None = None,
|
27
|
+
hx_post: str | None = None,
|
28
|
+
hx_put: str | None = None,
|
29
|
+
hx_patch: str | None = None,
|
30
|
+
hx_delete: str | None = None,
|
31
|
+
hx_params: str | None = None,
|
32
|
+
hx_push_url: Annotated[bool, "Replace the browser url by the ajax request"] = false,
|
33
|
+
full_width: Annotated[str, "Append tailwind class w-full to get full width"] = false,
|
23
34
|
#}
|
24
|
-
<button type="{{type
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
{
|
34
|
-
{{
|
35
|
+
<button type="{{ type }}"
|
36
|
+
name="{{ name }}"
|
37
|
+
value="{{ value }}"
|
38
|
+
{%- if id %} id="{{ id }}" {%- endif %}
|
39
|
+
{%- if hx_target %} hx-target="{{ hx_target }}" {%- endif %}
|
40
|
+
{%- if hx_swap %} hx-swap="{{ hx_swap }}" {%- endif %}
|
41
|
+
{%- if hx_select %} hx-select="{{ hx_select }}" {%- endif %}
|
42
|
+
{%- if onclick %} onclick="{{ onclick }}" {%- endif %}
|
43
|
+
{%- if hx_after_request %} hx-on::after-request="{{ hx_after_request }}" {%- endif %}
|
44
|
+
{%- if hx_vals %} hx-vals='{{ hx_vals|safe }}' {%- endif %}
|
45
|
+
{%- if hx_confirm %} hx-confirm="{{ hx_confirm }}" {%- endif %}
|
46
|
+
{%- if hx_get %} hx-get="{{ hx_get }}" {%- endif %}
|
47
|
+
{%- if hx_post %} hx-post="{{ hx_post }}" {%- endif %}
|
48
|
+
{%- if hx_put %} hx-put="{{ hx_put }}" {%- endif %}
|
49
|
+
{%- if hx_patch %} hx-patch="{{ hx_patch }}" {%- endif %}
|
50
|
+
{%- if hx_delete %} hx-delete="{{ hx_delete }}" {%- endif %}
|
51
|
+
{%- if hx_push_url %} hx-push-url="true" {%- endif %}
|
52
|
+
{%- if hx_params %} hx-params="{{ hx_params }}" {%- endif %}
|
53
|
+
{%- if aria_label %} aria-label="{{ aria_label }}" {%- endif %}
|
54
|
+
class="{% if full_width %}w-full {% endif %}{{ attrs.class or BUTTON_CLASS }}"
|
55
|
+
{%- if hidden %} hidden{% endif -%}
|
56
|
+
>
|
57
|
+
{{- content -}}
|
35
58
|
</button>
|
@@ -1,8 +1,22 @@
|
|
1
|
+
{# doc
|
2
|
+
Create html ``<input type="checkbox" />`` node.
|
3
|
+
#}
|
1
4
|
{# def
|
2
|
-
name,
|
3
|
-
id,
|
4
|
-
|
5
|
-
|
5
|
+
name: Annotated[str, "Name of the checkbox"],
|
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.CHECKBOX_CLASS`."
|
11
|
+
] = None,
|
12
|
+
value: Annotated[str | None, "http submitted value if the checkbox is checked"] = None,
|
13
|
+
checked: Annotated[bool, "Initialized the checkbox as ticked"] = False,
|
6
14
|
#}
|
7
|
-
<input
|
8
|
-
|
15
|
+
<input
|
16
|
+
name="{{ name }}"
|
17
|
+
type="checkbox"
|
18
|
+
{%- if id %} id="{{id}}" {%- endif %}
|
19
|
+
{%- if value is not none %} value="{{ value }}" {%- endif %}
|
20
|
+
class="{{ attrs.class or CHECKBOX_CLASS }}"
|
21
|
+
{%- if checked %} checked{% endif -%}
|
22
|
+
/>
|
fastlife/templates/Details.jinja
CHANGED
@@ -1,4 +1,32 @@
|
|
1
|
-
{#
|
2
|
-
|
3
|
-
|
1
|
+
{# doc
|
2
|
+
|
3
|
+
Produce a ``<details>`` html node in order to create a collapsible box.
|
4
|
+
|
5
|
+
.. code-block:: html
|
6
|
+
|
7
|
+
<Details>
|
8
|
+
<Summary :id="my-summary">
|
9
|
+
<H3 :class="H3_SUMMARY_CLASS">A title</H3>
|
10
|
+
</Summary>
|
11
|
+
<div>
|
12
|
+
Some content
|
13
|
+
</div>
|
14
|
+
</Details>
|
15
|
+
|
16
|
+
#}
|
17
|
+
{# def
|
18
|
+
id: Annotated[str | None, "unique identifier of the element."] = None,
|
19
|
+
class_: Annotated[
|
20
|
+
str | None,
|
21
|
+
"css class for the node, defaults to "
|
22
|
+
":attr:`fastlife.templating.renderer.constants.Constants.DETAILS_CLASS`."
|
23
|
+
] = None,
|
24
|
+
open: Annotated[bool, "open/close state."] = True,
|
25
|
+
#}
|
26
|
+
<details
|
27
|
+
{%- if id %} id="{{id}}" {% endif %}
|
28
|
+
class="{{attrs.class or DETAILS_CLASS}}"
|
29
|
+
{%- if open %} open {%- endif -%}
|
30
|
+
>
|
31
|
+
{{- content -}}
|
4
32
|
</details>
|
fastlife/templates/Form.jinja
CHANGED
@@ -1,11 +1,49 @@
|
|
1
|
+
{# doc
|
2
|
+
Create html ``<form>`` node with htmx support by default.
|
3
|
+
A :jinjax:component:`CsrfToken` will always be included in the form
|
4
|
+
and will be checked by the
|
5
|
+
:func:`csrf policy method <fastlife.security.csrf.check_csrf>`.
|
6
|
+
|
7
|
+
::
|
8
|
+
|
9
|
+
<Form :hx-post="true">
|
10
|
+
<Input name="name" placeholder="Bob" />
|
11
|
+
<Button>Submit</Button>
|
12
|
+
</Form>
|
13
|
+
|
14
|
+
#}
|
15
|
+
|
1
16
|
{# def
|
2
|
-
|
3
|
-
|
4
|
-
|
17
|
+
id: Annotated[str | None, "unique identifier of the element."] = None,
|
18
|
+
class_: Annotated[
|
19
|
+
str | None,
|
20
|
+
"css class for the node, defaults to "
|
21
|
+
":attr:`fastlife.templating.renderer.constants.Constants.FORM_CLASS`"
|
22
|
+
] = None,
|
23
|
+
method: Annotated[Literal["get", "post"] | None, "Http method used"] = None,
|
24
|
+
action: Annotated[str | None, "url where the form will be submitted"] = None,
|
25
|
+
hx_post: Annotated[
|
26
|
+
str | Literal[True] | None,
|
27
|
+
"url where the form will be submitted using htmx. if ``True``, the current url is used."\
|
28
|
+
] = None,
|
29
|
+
hx_disable: Annotated[
|
30
|
+
Literal[True] | None,
|
31
|
+
"if true, then htmx will be disabled for the form and for all its children nodes."
|
32
|
+
] = None,
|
5
33
|
#}
|
6
|
-
<form
|
7
|
-
|
8
|
-
|
34
|
+
<form
|
35
|
+
{%- if id %} id="{{id}}" {% endif %}
|
36
|
+
class="{{attrs.class or FORM_CLASS}}"
|
37
|
+
{%- if hx_disable %}
|
38
|
+
hx-disable
|
39
|
+
{%- else %}
|
40
|
+
{%- if hx_post is not none %}
|
41
|
+
hx-post="{% if hx_post is not true %}{{hx_post}}{% endif %}"
|
42
|
+
{%- endif %}
|
43
|
+
{%- endif %}
|
44
|
+
{%- if action is not none %} action="{{action}}" {%- endif %}
|
45
|
+
{%- if method %} method="{{method}}" {%- endif -%}
|
46
|
+
>
|
9
47
|
<CsrfToken />
|
10
|
-
{{ content }}
|
48
|
+
{{- content -}}
|
11
49
|
</form>
|
fastlife/templates/H1.jinja
CHANGED
@@ -1 +1,14 @@
|
|
1
|
-
|
1
|
+
{# doc html ``<h1>`` 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.H1_CLASS`."
|
8
|
+
] = None,
|
9
|
+
#}
|
10
|
+
<h1
|
11
|
+
{%- if id %} id="{{id}}" {%- endif %}
|
12
|
+
class="{{attrs.class or H1_CLASS}}">
|
13
|
+
{{- content -}}
|
14
|
+
</h1>
|
fastlife/templates/H2.jinja
CHANGED
@@ -1 +1,14 @@
|
|
1
|
-
|
1
|
+
{# doc html ``<h2>`` 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.H2_CLASS`."
|
8
|
+
] = None,
|
9
|
+
#}
|
10
|
+
<h2
|
11
|
+
{%- if id %} id="{{id}}" {%- endif %}
|
12
|
+
class="{{attrs.class or H2_CLASS}}">
|
13
|
+
{{- content -}}
|
14
|
+
</h2>
|
fastlife/templates/H3.jinja
CHANGED
@@ -1 +1,14 @@
|
|
1
|
-
|
1
|
+
{# doc html ``<h3>`` 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.H3_CLASS`."
|
8
|
+
] = None,
|
9
|
+
#}
|
10
|
+
<h3
|
11
|
+
{%- if id %} id="{{id}}" {%- endif %}
|
12
|
+
class="{{attrs.class or H3_CLASS}}">
|
13
|
+
{{- content -}}
|
14
|
+
</h3>
|
fastlife/templates/H4.jinja
CHANGED
@@ -1 +1,14 @@
|
|
1
|
-
|
1
|
+
{# doc html ``<h4>`` 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.H4_CLASS`."
|
8
|
+
] = None,
|
9
|
+
#}
|
10
|
+
<h4
|
11
|
+
{%- if id %} id="{{id}}" {%- endif %}
|
12
|
+
class="{{attrs.class or H4_CLASS}}">
|
13
|
+
{{- content -}}
|
14
|
+
</h4>
|
fastlife/templates/H5.jinja
CHANGED
@@ -1 +1,14 @@
|
|
1
|
-
|
1
|
+
{# doc html ``<h5>`` 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.H5_CLASS`."
|
8
|
+
] = None,
|
9
|
+
#}
|
10
|
+
<h5
|
11
|
+
{%- if id %} id="{{id}}" {%- endif %}
|
12
|
+
class="{{attrs.class or H5_CLASS}}">
|
13
|
+
{{- content -}}
|
14
|
+
</h5>
|
fastlife/templates/H6.jinja
CHANGED
@@ -1 +1,14 @@
|
|
1
|
-
|
1
|
+
{# doc html ``<h6>`` 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.H6_CLASS`."
|
8
|
+
] = None,
|
9
|
+
#}
|
10
|
+
<h6
|
11
|
+
{%- if id %} id="{{id}}" {%- endif %}
|
12
|
+
class="{{attrs.class or H6_CLASS}}">
|
13
|
+
{{- content -}}
|
14
|
+
</h6>
|
fastlife/templates/Hidden.jinja
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{# def
|
2
|
-
name,
|
3
|
-
value,
|
4
|
-
id
|
2
|
+
name: Annotated[str, "submitted name in the form"],
|
3
|
+
value: Annotated[str, "submitted value in the form"],
|
4
|
+
id: Annotated[str | None, "unique identifier of the element."] = None,
|
5
5
|
#}
|
6
6
|
<input name="{{name}}" value="{{value}}" type="hidden" {%if id %}id="{{id}}" {%endif%} />
|
fastlife/templates/Input.jinja
CHANGED
@@ -1,12 +1,25 @@
|
|
1
|
+
{# doc
|
2
|
+
Produce ``<input>`` node.
|
3
|
+
#}
|
1
4
|
{# def
|
2
|
-
name,
|
3
|
-
value="",
|
4
|
-
type="text",
|
5
|
-
id
|
6
|
-
|
7
|
-
|
5
|
+
name: Annotated[str, "submitted name in the form"],
|
6
|
+
value: Annotated[str, "submitted value in the form"] = "",
|
7
|
+
type: Annotated[str, "type of the control"] = "text",
|
8
|
+
id: Annotated[str | None, "unique identifier of the element."] = None,
|
9
|
+
class_: Annotated[
|
10
|
+
str | None,
|
11
|
+
"css class for the node, defaults to "
|
12
|
+
":attr:`fastlife.templating.renderer.constants.Constants.INPUT_CLASS`."
|
13
|
+
] = None,
|
14
|
+
aria_label: Annotated[str | None, "aria-label"] = None,
|
15
|
+
placeholder: Annotated[
|
16
|
+
str | None,
|
17
|
+
"brief hint to the user as to what kind of information is expected in the field."
|
18
|
+
] = None,
|
8
19
|
#}
|
9
20
|
|
10
|
-
<input name="{{name}}" value="{{value}}" type="{{type}}"
|
11
|
-
|
21
|
+
<input name="{{name}}" value="{{value}}" type="{{type}}"
|
22
|
+
{%- if id %} id="{{id}}" {%- endif %}
|
23
|
+
{%- if aria_label %} aria-label="{{aria_label}}" {%- endif %}
|
24
|
+
{%- if placeholder %} placeholder="{{placeholder}}" {%- endif %}
|
12
25
|
class="{{attrs.class or INPUT_CLASS}}" />
|