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.
Files changed (90) hide show
  1. fastlife/__init__.py +2 -2
  2. fastlife/config/__init__.py +13 -0
  3. fastlife/{configurator → config}/configurator.py +64 -41
  4. fastlife/{configurator → config}/registry.py +2 -10
  5. fastlife/{configurator → config}/settings.py +7 -3
  6. fastlife/middlewares/__init__.py +7 -0
  7. fastlife/middlewares/base.py +24 -0
  8. fastlife/middlewares/reverse_proxy/__init__.py +16 -0
  9. fastlife/middlewares/reverse_proxy/x_forwarded.py +1 -14
  10. fastlife/middlewares/session/__init__.py +16 -0
  11. fastlife/middlewares/session/middleware.py +6 -1
  12. fastlife/middlewares/session/serializer.py +21 -0
  13. fastlife/request/__init__.py +5 -0
  14. fastlife/request/{model_result.py → form.py} +21 -9
  15. fastlife/request/form_data.py +28 -3
  16. fastlife/request/request.py +18 -0
  17. fastlife/routing/__init__.py +7 -0
  18. fastlife/routing/route.py +45 -0
  19. fastlife/routing/router.py +12 -4
  20. fastlife/security/__init__.py +1 -0
  21. fastlife/security/csrf.py +29 -11
  22. fastlife/security/policy.py +6 -2
  23. fastlife/shared_utils/__init__.py +1 -0
  24. fastlife/shared_utils/infer.py +7 -0
  25. fastlife/shared_utils/resolver.py +10 -2
  26. fastlife/templates/A.jinja +33 -9
  27. fastlife/templates/Button.jinja +55 -32
  28. fastlife/templates/Checkbox.jinja +20 -6
  29. fastlife/templates/CsrfToken.jinja +4 -0
  30. fastlife/templates/Details.jinja +31 -3
  31. fastlife/templates/Form.jinja +45 -7
  32. fastlife/templates/H1.jinja +14 -1
  33. fastlife/templates/H2.jinja +14 -1
  34. fastlife/templates/H3.jinja +14 -1
  35. fastlife/templates/H4.jinja +14 -1
  36. fastlife/templates/H5.jinja +14 -1
  37. fastlife/templates/H6.jinja +14 -1
  38. fastlife/templates/Hidden.jinja +3 -3
  39. fastlife/templates/Input.jinja +21 -8
  40. fastlife/templates/Label.jinja +18 -2
  41. fastlife/templates/Option.jinja +14 -2
  42. fastlife/templates/P.jinja +14 -2
  43. fastlife/templates/Radio.jinja +34 -12
  44. fastlife/templates/Select.jinja +15 -4
  45. fastlife/templates/Summary.jinja +13 -2
  46. fastlife/templates/Table.jinja +12 -1
  47. fastlife/templates/Tbody.jinja +11 -1
  48. fastlife/templates/Td.jinja +12 -1
  49. fastlife/templates/Textarea.jinja +18 -0
  50. fastlife/templates/Tfoot.jinja +11 -1
  51. fastlife/templates/Th.jinja +12 -1
  52. fastlife/templates/Thead.jinja +11 -1
  53. fastlife/templates/Tr.jinja +11 -1
  54. fastlife/templates/pydantic_form/Boolean.jinja +3 -2
  55. fastlife/templates/pydantic_form/Checklist.jinja +3 -5
  56. fastlife/templates/pydantic_form/Dropdown.jinja +3 -2
  57. fastlife/templates/pydantic_form/Error.jinja +4 -3
  58. fastlife/templates/pydantic_form/Hidden.jinja +2 -1
  59. fastlife/templates/pydantic_form/Hint.jinja +2 -1
  60. fastlife/templates/pydantic_form/Model.jinja +16 -3
  61. fastlife/templates/pydantic_form/Sequence.jinja +15 -6
  62. fastlife/templates/pydantic_form/Text.jinja +2 -2
  63. fastlife/templates/pydantic_form/Textarea.jinja +32 -0
  64. fastlife/templates/pydantic_form/Union.jinja +7 -1
  65. fastlife/templates/pydantic_form/Widget.jinja +6 -3
  66. fastlife/templating/binding.py +18 -4
  67. fastlife/templating/renderer/__init__.py +3 -1
  68. fastlife/templating/renderer/abstract.py +21 -8
  69. fastlife/templating/renderer/constants.py +82 -0
  70. fastlife/templating/renderer/jinjax.py +269 -6
  71. fastlife/templating/renderer/widgets/base.py +43 -7
  72. fastlife/templating/renderer/widgets/boolean.py +21 -0
  73. fastlife/templating/renderer/widgets/checklist.py +23 -0
  74. fastlife/templating/renderer/widgets/dropdown.py +22 -2
  75. fastlife/templating/renderer/widgets/factory.py +100 -29
  76. fastlife/templating/renderer/widgets/hidden.py +16 -0
  77. fastlife/templating/renderer/widgets/model.py +7 -1
  78. fastlife/templating/renderer/widgets/sequence.py +8 -6
  79. fastlife/templating/renderer/widgets/text.py +80 -4
  80. fastlife/templating/renderer/widgets/union.py +25 -2
  81. fastlife/testing/testclient.py +3 -3
  82. fastlife/views/pydantic_form.py +2 -2
  83. {fastlifeweb-0.9.7.dist-info → fastlifeweb-0.11.0.dist-info}/METADATA +4 -9
  84. {fastlifeweb-0.9.7.dist-info → fastlifeweb-0.11.0.dist-info}/RECORD +86 -84
  85. fastlife/configurator/__init__.py +0 -4
  86. fastlife/configurator/base.py +0 -9
  87. fastlife/configurator/route_handler.py +0 -29
  88. fastlife/templates/__init__.py +0 -0
  89. {fastlifeweb-0.9.7.dist-info → fastlifeweb-0.11.0.dist-info}/LICENSE +0 -0
  90. {fastlifeweb-0.9.7.dist-info → fastlifeweb-0.11.0.dist-info}/WHEEL +0 -0
@@ -1,2 +1,18 @@
1
-
2
- <label for="{{attrs.for}}" class="{{attrs.class or LABEL_CLASS}}">{{content}}</label>
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>
@@ -1,4 +1,16 @@
1
- {# def value, id=None, selected=False #}
2
- <option value="{{value}}" {%if id%}id="{{id}}" {% endif%} {%if selected%}selected{% endif%}>
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>
@@ -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 {% if id %}id="{{id}}" {% endif %} class="{{ attrs.class or P_CLASS}}">{{content}}</p>
12
+ <p
13
+ {%- if id %} id="{{id}}" {% endif %}
14
+ class="{{ attrs.class or P_CLASS }}">
15
+ {{- content -}}
16
+ </p>
@@ -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
- id,
5
- value,
6
- checked=false,
7
- disabled=false,
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="{{attrs.div_class or RADIO_DIV_CLASS}}">
11
- <input type="radio" name="{{name}}" id="{{id}}" value="{{value}}" {% if checked %}checked{% endif %}
12
- class="{{attrs.class or RADIO_INPUT_CLASS}}" {% if onclick %}onclick="{{onclick}}" {% endif %} {%if
13
- disabled%}disabled{%endif%}>
14
- <Label :for="id" :class="attrs.label_class or RADIO_LABEL_CLASS">{{label}}</Label>
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>
@@ -1,9 +1,20 @@
1
+ {# doc
2
+ Create html ``<select>`` node.
3
+ #}
1
4
  {# def
2
- name,
3
- id,
4
- multiple=False,
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}}" id="{{id}}" class="{{attrs.class or SELECT_CLASS}}" {% if multiple %}multiple{% endif %}>
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>
@@ -1,6 +1,17 @@
1
- {# def id, open=True #}
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="flex items-center items-center font-medium cursor-pointer"
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'"
@@ -1,3 +1,14 @@
1
- <table class="{{attrs.class or TABLE_CLASS}}">
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>
@@ -1,3 +1,13 @@
1
- <tbody>
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>
@@ -1,3 +1,14 @@
1
- <td class="{{attrs.class or TD_CLASS}}">
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>
@@ -1,3 +1,13 @@
1
- <tfoot>
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>
@@ -1,3 +1,14 @@
1
- <th class="{{attrs.class or TH_CLASS}}">
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>
@@ -1,3 +1,13 @@
1
- <thead>
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>
@@ -1,3 +1,13 @@
1
- <tr class="{{attrs.class}}">
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
- {# def widget #}
2
- <pydantic_form.Widget :widget="widget" :removable="widget.removable">
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
- {# def
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
- {# def widget #}
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" :removable="widget.removable">
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,4 +1,5 @@
1
- {# def text #}
2
- {% if text %}
1
+ {# doc display an error for a field. #}
2
+ {# def text: Annotated[str | None, "error message"] #}
3
+ {%- if text -%}
3
4
  <span class="mt-2 text-sm text-danger-500 dark:text-danger-400">{{text}}</span>
4
- {% endif %}
5
+ {%- endif -%}
@@ -1,2 +1,3 @@
1
- {# def widget #}
1
+ {# doc Render a hidden input field for a field. #}
2
+ {# def widget: Annotated[fastlife.templating.renderer.widgets.hidden.HiddenWidget, "widget to display."] #}
2
3
  <Hidden :name="widget.name" :value="widget.value" :id="widget.id" />
@@ -1,4 +1,5 @@
1
- {# def text #}
1
+ {# Display a hint message for a field. #}
2
+ {# def text: Annotated[str | None, "hint text."] #}
2
3
  {% if text %}
3
4
  <span class="mt-2 text-sm text-neutral-500 dark:text-neutral-400">{{text}}</span>
4
5
  {% endif %}
@@ -1,7 +1,15 @@
1
- {# def widget, children_widget #}
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" :removable="widget.removable">
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
- {# def widget, children_widgets, type #}
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" :removable="widget.removable">
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
- {% for child in children_widgets %}
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" :removable="widget.removable">
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
- {# def widget, child, types, parent_type #}
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
- {# def widget, removable=false #}
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 %}
@@ -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, Request, Response
7
+ from fastapi import Depends, Response
4
8
 
5
- from fastlife.configurator.registry import Registry
9
+ from fastlife.request import Request
6
10
  from fastlife.security.csrf import create_csrf_token
7
11
 
8
12
  Template = Callable[..., Response]
9
- TemplateEngine = Callable[["Registry", Request], Template]
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.model_result import ModelResult
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: ModelResult[Any], *, token: Optional[str] = None
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
- <form action="" method="post">
74
+ <Form action="" method="post">
65
75
  {{ pydantic_form(model) }}
66
- </form>
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