You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tooling.apache.org by ar...@apache.org on 2026/08/05 13:52:27 UTC
(tooling-trusted-releases) branch arm updated: Give mandatory form fields an asterisk and fix error with file form inputs validation
This is an automated email from the ASF dual-hosted git repository.
alitheg pushed a commit to branch arm
in repository https://gitbox.apache.org/repos/asf/tooling-trusted-releases.git
The following commit(s) were added to refs/heads/arm by this push:
new 1b8611f0 Give mandatory form fields an asterisk and fix error with file form inputs validation
1b8611f0 is described below
commit 1b8611f024d9f32f94dea897181ca3130ac05772
Author: Alastair McFarlane <ar...@apache.org>
AuthorDate: Wed Aug 5 14:52:09 2026 +0100
Give mandatory form fields an asterisk and fix error with file form inputs validation
---
atr/form.py | 31 +++++++++++++++++++++++++------
atr/shared/keys.py | 1 +
atr/shared/projects.py | 3 +++
3 files changed, 29 insertions(+), 6 deletions(-)
diff --git a/atr/form.py b/atr/form.py
index 6365b9a4..0b496f4e 100644
--- a/atr/form.py
+++ b/atr/form.py
@@ -160,11 +160,12 @@ def flash_error_summary(errors: list[pydantic_core.ErrorDetails], flash_data: di
def json_suitable(field_value: Any) -> Any:
if isinstance(field_value, datastructures.FileStorage):
return field_value.filename
- elif isinstance(field_value, list):
- if all(isinstance(f, datastructures.FileStorage) for f in field_value):
- return [f.filename for f in field_value]
- else:
- return field_value
+ # A model-level validation error carries the whole submitted mapping as its input, so a file
+ # nested inside a dict or list has to be reduced to its name too, not just one at the top level
+ if isinstance(field_value, dict):
+ return {key: json_suitable(value) for key, value in field_value.items()}
+ if isinstance(field_value, list):
+ return [json_suitable(item) for item in field_value]
return field_value
@@ -176,6 +177,7 @@ def label(
widget: Widget | None = None,
max_length: int | None = None,
readonly: bool = False,
+ required: bool = False,
**kwargs,
) -> Any:
extra: dict[str, Any] = {}
@@ -184,6 +186,10 @@ def label(
if documentation is not None:
extra["documentation"] = documentation
extra["readonly"] = readonly
+ # A field with no default is already marked required from is_required. This flag is for the fields
+ # that carry an empty default - a list, an optional - but a validator still makes them mandatory
+ if required:
+ extra["required"] = True
if len(kwargs) > 0:
extra.update(kwargs)
return pydantic.Field(default, description=description, json_schema_extra=extra, max_length=max_length)
@@ -877,6 +883,11 @@ def _render_field_value(
return field_value
+def _label_marks_required(field_info: pydantic.fields.FieldInfo) -> bool:
+ extra = field_info.json_schema_extra
+ return isinstance(extra, dict) and bool(extra.get("required"))
+
+
def _render_row( # noqa: C901
field_info: pydantic.fields.FieldInfo,
field_name: str,
@@ -926,7 +937,15 @@ def _render_row( # noqa: C901
label_classes = f"{label_col_class} col-form-label text-sm-end"
label_classes_with_error = f"{label_classes} text-danger" if has_flash_error else label_classes
- label_elem = htpy.label(for_=field_name, class_=label_classes_with_error)[label_text]
+ label_content: list[Any] = [label_text]
+ # A field with no default is required, but a collection type carries an empty default even when a
+ # validator makes it mandatory, so `required=True` on the label covers what is_required can't see
+ if is_required or _label_marks_required(field_info):
+ # The asterisk is hidden from screen readers, which pick the requirement up from the
+ # visually-hidden note beside it instead
+ label_content.append(htm.span(".text-danger.ms-1", aria_hidden="true")["*"])
+ label_content.append(htm.span(".visually-hidden")[" (required)"])
+ label_elem = htpy.label(for_=field_name, class_=label_classes_with_error)[label_content]
widget_elem = _render_widget(
field_name=field_name,
diff --git a/atr/shared/keys.py b/atr/shared/keys.py
index 6b3aa337..e4a7b5ae 100644
--- a/atr/shared/keys.py
+++ b/atr/shared/keys.py
@@ -56,6 +56,7 @@ class AddOpenPGPKeyForm(form.Form):
"Associating your key with a committee adds it to that committee's public KEYS file on the"
" ASF distribution site, which ATR and downstream users rely on to verify the signatures on its"
" releases. Associate a key with a committee when you sign releases for it.",
+ required=True,
)
@pydantic.model_validator(mode="after")
diff --git a/atr/shared/projects.py b/atr/shared/projects.py
index 23357f25..02cc29d4 100644
--- a/atr/shared/projects.py
+++ b/atr/shared/projects.py
@@ -301,6 +301,7 @@ class EditMetadataForm(form.Form):
homepage: form.OptionalURL = form.label(
"Homepage",
"Project website URL.",
+ required=True,
)
lifecycle_page: form.OptionalURL = form.label(
"Lifecycle page",
@@ -309,6 +310,7 @@ class EditMetadataForm(form.Form):
download_page: form.OptionalURL = form.label(
"Download page",
"URL of the project's official download page.",
+ required=True,
)
bug_database: form.OptionalURL = form.label(
"Bug database",
@@ -323,6 +325,7 @@ class EditMetadataForm(form.Form):
"Repository URLs, one per line.",
widget=form.Widget.TEXTAREA,
rows=3,
+ required=True,
)
standards: form.URIList = form.label(
"Standards",
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@tooling.apache.org
For additional commands, e-mail: commits-help@tooling.apache.org