This is an automated email from the ASF dual-hosted git repository.
sbp pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/tooling-trusted-releases.git
The following commit(s) were added to refs/heads/main by this push:
new 0da003c Allow custom widgets
0da003c is described below
commit 0da003cef6dbdf93b5786cbb62f644e118a3ec98
Author: Sean B. Palmer <[email protected]>
AuthorDate: Sun Nov 9 12:32:32 2025 +0000
Allow custom widgets
---
atr/form.py | 16 ++++++++++++++++
atr/get/test.py | 12 ++++++++++++
atr/post/test.py | 3 ++-
atr/shared/test.py | 1 +
4 files changed, 31 insertions(+), 1 deletion(-)
diff --git a/atr/form.py b/atr/form.py
index 81ad0d7..dec43a5 100644
--- a/atr/form.py
+++ b/atr/form.py
@@ -55,6 +55,7 @@ class Empty(Form):
class Widget(enum.Enum):
CHECKBOX = "checkbox"
CHECKBOXES = "checkboxes"
+ CUSTOM = "custom"
EMAIL = "email"
FILE = "file"
FILES = "files"
@@ -202,6 +203,7 @@ async def render(
defaults: dict[str, Any] | None = None,
errors: dict[str, list[str]] | None = None,
use_error_data: bool = True,
+ custom: dict[str, htm.Element | htm.VoidElement] | None = None,
) -> htm.Element:
if action is None:
action = quart.request.path
@@ -229,6 +231,7 @@ async def render(
defaults,
errors,
textarea_rows,
+ custom,
)
if hidden_field:
hidden_fields.append(hidden_field)
@@ -250,6 +253,10 @@ async def render(
submit_row = htm.div(".row")[submit_div[submit_div_contents]]
form_children.append(submit_row)
+ if custom:
+ unused = ", ".join(custom.keys())
+ raise ValueError(f"Custom widgets provided but not used: {unused}")
+
return htm.form(form_classes, action=action, method="post",
enctype="multipart/form-data")[form_children]
@@ -378,6 +385,7 @@ def _render_widget( # noqa: C901
field_errors: list[str] | None,
is_required: bool,
textarea_rows: int,
+ custom: dict[str, htm.Element | htm.VoidElement] | None,
) -> htm.Element | htm.VoidElement:
widget_type = _get_widget_type(field_info)
widget_classes = _get_widget_classes(widget_type, field_errors)
@@ -422,6 +430,12 @@ def _render_widget( # noqa: C901
elements.extend(checkboxes)
widget = htm.div[checkboxes]
+ case Widget.CUSTOM:
+ if custom and (field_name in custom):
+ widget = custom.pop(field_name)
+ else:
+ widget = htm.div[f"Custom widget for {field_name} not
provided"]
+
case Widget.EMAIL:
attrs = {**base_attrs, "type": "email"}
if field_value:
@@ -643,6 +657,7 @@ def _render_row(
defaults: dict[str, Any] | None,
errors: dict[str, list[str]] | None,
textarea_rows: int,
+ custom: dict[str, htm.Element | htm.VoidElement] | None,
) -> tuple[htm.VoidElement | None, htm.Element | None]:
widget_type = _get_widget_type(field_info)
has_flash_error = field_name in flash_error_data
@@ -674,6 +689,7 @@ def _render_row(
field_errors=field_errors,
is_required=is_required,
textarea_rows=textarea_rows,
+ custom=custom,
)
row_div = htm.div(".mb-3.row")
diff --git a/atr/get/test.py b/atr/get/test.py
index 8998d93..e24e986 100644
--- a/atr/get/test.py
+++ b/atr/get/test.py
@@ -91,10 +91,22 @@ async def test_multiple(session: web.Committer | None) ->
str:
@get.public("/test/single")
async def test_single(session: web.Committer | None) -> str:
+ import htpy
+
+ vote_widget = htpy.div(class_="btn-group", role="group")[
+ htpy.input(type="radio", class_="btn-check", name="vote", id="vote_0",
value="+1", autocomplete="off"),
+ htpy.label(class_="btn btn-outline-success", for_="vote_0")["+1"],
+ htpy.input(type="radio", class_="btn-check", name="vote", id="vote_1",
value="0", autocomplete="off"),
+ htpy.label(class_="btn btn-outline-secondary", for_="vote_1")["0"],
+ htpy.input(type="radio", class_="btn-check", name="vote", id="vote_2",
value="-1", autocomplete="off"),
+ htpy.label(class_="btn btn-outline-danger", for_="vote_2")["-1"],
+ ]
+
single_form = await form.render(
model_cls=shared.test.SingleForm,
submit_label="Submit",
action="/test/single",
+ custom={"vote": vote_widget},
)
forms_html = htm.div[
diff --git a/atr/post/test.py b/atr/post/test.py
index d5a4a01..b64a948 100644
--- a/atr/post/test.py
+++ b/atr/post/test.py
@@ -66,7 +66,8 @@ async def test_single(session: web.Committer | None, form:
shared.test.SingleFor
f" email={form.email},"
f" message={form.message},"
f" files={file_names},"
- f" compatibility={compatibility_names}"
+ f" compatibility={compatibility_names},"
+ f" vote={form.vote}"
)
log.info(msg)
await quart.flash(msg, "success")
diff --git a/atr/shared/test.py b/atr/shared/test.py
index 971f477..7fd3a7b 100644
--- a/atr/shared/test.py
+++ b/atr/shared/test.py
@@ -57,6 +57,7 @@ class SingleForm(form.Form):
message: str = form.label("Message")
files: form.FileList = form.label("Files to upload")
compatibility: form.Set[Compatibility] = form.label("Compatibility")
+ vote: Literal["+1", "0", "-1"] = form.label("Vote",
widget=form.Widget.CUSTOM)
@pydantic.field_validator("email")
@classmethod
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]