From e7e2f9c0be4cb5f0f939846c248035b5ae6f509f Mon Sep 17 00:00:00 2001 From: Berry den Hartog <38954346+berrydenhartog@users.noreply.github.com> Date: Mon, 21 Oct 2024 13:18:06 +0000 Subject: [PATCH] Add error message above field --- amt/core/exception_handlers.py | 25 ++++++++++- amt/locale/base.pot | 38 +++++++++------- amt/locale/en_US/LC_MESSAGES/messages.mo | Bin 451 -> 451 bytes amt/locale/en_US/LC_MESSAGES/messages.po | 38 +++++++++------- amt/locale/nl_NL/LC_MESSAGES/messages.mo | Bin 10753 -> 10909 bytes amt/locale/nl_NL/LC_MESSAGES/messages.po | 42 +++++++++++------- amt/site/static/scss/layout.scss | 5 +++ .../_RequestValidationError_400.html.j2 | 20 +++++++++ amt/site/templates/projects/new.html.j2 | 7 ++- tests/api/routes/test_projects.py | 2 +- 10 files changed, 125 insertions(+), 52 deletions(-) create mode 100644 amt/site/templates/errors/_RequestValidationError_400.html.j2 diff --git a/amt/core/exception_handlers.py b/amt/core/exception_handlers.py index 1806eb23..e8eac482 100644 --- a/amt/core/exception_handlers.py +++ b/amt/core/exception_handlers.py @@ -1,5 +1,8 @@ import logging +from gettext import gettext as _ +from typing import Any +from babel.support import NullTranslations from fastapi import Request, status from fastapi.exceptions import RequestValidationError from fastapi.responses import HTMLResponse @@ -13,6 +16,21 @@ logger = logging.getLogger(__name__) +CUSTOM_MESSAGES = { + "string_too_short": _("String should have at least {min_length} characters"), + "missing": _("Field required"), +} + + +def translate_pydantic_exception(err: dict[str, Any], translations: NullTranslations) -> str: + message: str | None = CUSTOM_MESSAGES.get(err["type"], None) + + if message: + custom_message = translations.gettext(message) + return custom_message.format(**err["ctx"]) if "ctx" in err else custom_message + + return err["msg"] + async def general_exception_handler(request: Request, exc: Exception) -> HTMLResponse: exception_name = exc.__class__.__name__ @@ -27,8 +45,10 @@ async def general_exception_handler(request: Request, exc: Exception) -> HTMLRes elif isinstance(exc, StarletteHTTPException): message = AMTNotFound().getmessage(translations) if exc.status_code == status.HTTP_404_NOT_FOUND else exc.detail elif isinstance(exc, RequestValidationError): - messages: list[str] = [f"{error['loc'][-1]}: {error['msg']}" for error in exc.errors()] - message = "\n".join(messages) + # i assume only pydantic errors get into this section + message = exc.errors() + for err in message: + err["msg"] = translate_pydantic_exception(err, translations) status_code = status.HTTP_500_INTERNAL_SERVER_ERROR if isinstance(exc, StarletteHTTPException): @@ -42,6 +62,7 @@ async def general_exception_handler(request: Request, exc: Exception) -> HTMLRes if request.state.htmx else f"errors/{exception_name}_{status_code}.html.j2" ) + fallback_template_name = "errors/_Exception.html.j2" if request.state.htmx else "errors/Exception.html.j2" response: HTMLResponse | None = None diff --git a/amt/locale/base.pot b/amt/locale/base.pot index 72295496..750d559a 100644 --- a/amt/locale/base.pot +++ b/amt/locale/base.pot @@ -133,7 +133,7 @@ msgstr "" msgid "Model" msgstr "" -#: amt/api/navigation.py:59 amt/site/templates/projects/new.html.j2:145 +#: amt/api/navigation.py:59 amt/site/templates/projects/new.html.j2:147 msgid "Instruments" msgstr "" @@ -161,6 +161,14 @@ msgstr "" msgid "Exception of application" msgstr "" +#: amt/core/exception_handlers.py:20 +msgid "String should have at least {min_length} characters" +msgstr "" + +#: amt/core/exception_handlers.py:21 +msgid "Field required" +msgstr "" + #: amt/core/exceptions.py:20 msgid "" "An error occurred while configuring the options for '{field}'. Please " @@ -415,7 +423,7 @@ msgstr "" #: amt/site/templates/parts/filter_list.html.j2:99 #: amt/site/templates/projects/details_info.html.j2:24 -#: amt/site/templates/projects/new.html.j2:37 +#: amt/site/templates/projects/new.html.j2:38 msgid "Lifecycle" msgstr "" @@ -601,27 +609,27 @@ msgstr "" msgid "Algorithm System name" msgstr "" -#: amt/site/templates/projects/new.html.j2:26 +#: amt/site/templates/projects/new.html.j2:27 msgid "Name of the algorithm system" msgstr "" -#: amt/site/templates/projects/new.html.j2:39 +#: amt/site/templates/projects/new.html.j2:41 msgid "Select the lifecycle your algorithm system is currently in." msgstr "" -#: amt/site/templates/projects/new.html.j2:40 +#: amt/site/templates/projects/new.html.j2:42 msgid "For more information on lifecycle, read the" msgstr "" -#: amt/site/templates/projects/new.html.j2:43 +#: amt/site/templates/projects/new.html.j2:45 msgid "Algorithm Framework" msgstr "" -#: amt/site/templates/projects/new.html.j2:61 +#: amt/site/templates/projects/new.html.j2:63 msgid "AI Act Profile" msgstr "" -#: amt/site/templates/projects/new.html.j2:63 +#: amt/site/templates/projects/new.html.j2:65 msgid "" "The AI Act profile provides insight into, among other things, the type of" " AI system and the associated obligations from the European AI Act. If " @@ -630,33 +638,33 @@ msgid "" "tree." msgstr "" -#: amt/site/templates/projects/new.html.j2:70 +#: amt/site/templates/projects/new.html.j2:72 msgid "Find your AI Act profile" msgstr "" -#: amt/site/templates/projects/new.html.j2:128 +#: amt/site/templates/projects/new.html.j2:130 msgid "Yes" msgstr "" -#: amt/site/templates/projects/new.html.j2:138 +#: amt/site/templates/projects/new.html.j2:140 msgid "No" msgstr "" -#: amt/site/templates/projects/new.html.j2:147 +#: amt/site/templates/projects/new.html.j2:149 msgid "" "Overview of instruments for the responsible development, deployment, " "assessment and monitoring of algorithms and AI-systems." msgstr "" -#: amt/site/templates/projects/new.html.j2:155 +#: amt/site/templates/projects/new.html.j2:157 msgid "Choose one or more instruments" msgstr "" -#: amt/site/templates/projects/new.html.j2:177 +#: amt/site/templates/projects/new.html.j2:181 msgid "Create Algorithm System" msgstr "" -#: amt/site/templates/projects/new.html.j2:195 +#: amt/site/templates/projects/new.html.j2:198 msgid "Copy results and close" msgstr "" diff --git a/amt/locale/en_US/LC_MESSAGES/messages.mo b/amt/locale/en_US/LC_MESSAGES/messages.mo index 1e457b00d634902124e4efe51dd6369c185ab9bc..222843522673a1cea30b02687915c0aaddf31f3b 100644 GIT binary patch delta 27 icmX@ie3*Gchk%iRk%_LMfv%CUf}x?6k?F=6+Kd2f{RcGw delta 13 VcmX@ie3*Gc$HWa{8|SGq0stvh1#tiX diff --git a/amt/locale/en_US/LC_MESSAGES/messages.po b/amt/locale/en_US/LC_MESSAGES/messages.po index 4a280015..d3ea5c28 100644 --- a/amt/locale/en_US/LC_MESSAGES/messages.po +++ b/amt/locale/en_US/LC_MESSAGES/messages.po @@ -134,7 +134,7 @@ msgstr "" msgid "Model" msgstr "" -#: amt/api/navigation.py:59 amt/site/templates/projects/new.html.j2:145 +#: amt/api/navigation.py:59 amt/site/templates/projects/new.html.j2:147 msgid "Instruments" msgstr "" @@ -162,6 +162,14 @@ msgstr "" msgid "Exception of application" msgstr "" +#: amt/core/exception_handlers.py:20 +msgid "String should have at least {min_length} characters" +msgstr "" + +#: amt/core/exception_handlers.py:21 +msgid "Field required" +msgstr "" + #: amt/core/exceptions.py:20 msgid "" "An error occurred while configuring the options for '{field}'. Please " @@ -416,7 +424,7 @@ msgstr "" #: amt/site/templates/parts/filter_list.html.j2:99 #: amt/site/templates/projects/details_info.html.j2:24 -#: amt/site/templates/projects/new.html.j2:37 +#: amt/site/templates/projects/new.html.j2:38 msgid "Lifecycle" msgstr "" @@ -602,27 +610,27 @@ msgstr "" msgid "Algorithm System name" msgstr "" -#: amt/site/templates/projects/new.html.j2:26 +#: amt/site/templates/projects/new.html.j2:27 msgid "Name of the algorithm system" msgstr "" -#: amt/site/templates/projects/new.html.j2:39 +#: amt/site/templates/projects/new.html.j2:41 msgid "Select the lifecycle your algorithm system is currently in." msgstr "" -#: amt/site/templates/projects/new.html.j2:40 +#: amt/site/templates/projects/new.html.j2:42 msgid "For more information on lifecycle, read the" msgstr "" -#: amt/site/templates/projects/new.html.j2:43 +#: amt/site/templates/projects/new.html.j2:45 msgid "Algorithm Framework" msgstr "" -#: amt/site/templates/projects/new.html.j2:61 +#: amt/site/templates/projects/new.html.j2:63 msgid "AI Act Profile" msgstr "" -#: amt/site/templates/projects/new.html.j2:63 +#: amt/site/templates/projects/new.html.j2:65 msgid "" "The AI Act profile provides insight into, among other things, the type of" " AI system and the associated obligations from the European AI Act. If " @@ -631,33 +639,33 @@ msgid "" "tree." msgstr "" -#: amt/site/templates/projects/new.html.j2:70 +#: amt/site/templates/projects/new.html.j2:72 msgid "Find your AI Act profile" msgstr "" -#: amt/site/templates/projects/new.html.j2:128 +#: amt/site/templates/projects/new.html.j2:130 msgid "Yes" msgstr "" -#: amt/site/templates/projects/new.html.j2:138 +#: amt/site/templates/projects/new.html.j2:140 msgid "No" msgstr "" -#: amt/site/templates/projects/new.html.j2:147 +#: amt/site/templates/projects/new.html.j2:149 msgid "" "Overview of instruments for the responsible development, deployment, " "assessment and monitoring of algorithms and AI-systems." msgstr "" -#: amt/site/templates/projects/new.html.j2:155 +#: amt/site/templates/projects/new.html.j2:157 msgid "Choose one or more instruments" msgstr "" -#: amt/site/templates/projects/new.html.j2:177 +#: amt/site/templates/projects/new.html.j2:181 msgid "Create Algorithm System" msgstr "" -#: amt/site/templates/projects/new.html.j2:195 +#: amt/site/templates/projects/new.html.j2:198 msgid "Copy results and close" msgstr "" diff --git a/amt/locale/nl_NL/LC_MESSAGES/messages.mo b/amt/locale/nl_NL/LC_MESSAGES/messages.mo index 8e677db13b144f4be211463bc4117101fd395a30..029d7cb798975a19493b601aaafc5e8895fe1790 100644 GIT binary patch delta 2635 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 + {% for msg in message %} +
+ + {{ msg['loc'][-1] }}: {{ msg['msg'] }} +
+ {% endfor %} + +{% for msg in message %} + +{% endfor %} diff --git a/amt/site/templates/projects/new.html.j2 b/amt/site/templates/projects/new.html.j2 index d6cf3037..44e36496 100644 --- a/amt/site/templates/projects/new.html.j2 +++ b/amt/site/templates/projects/new.html.j2 @@ -22,6 +22,7 @@
+
+

{% trans %}Select the lifecycle your algorithm system is currently in.{% endtrans %} {% trans %}For more information on lifecycle, read the{% endtrans %} @@ -170,15 +172,16 @@

+

-
diff --git a/tests/api/routes/test_projects.py b/tests/api/routes/test_projects.py index e1bf9dee..cdd09045 100644 --- a/tests/api/routes/test_projects.py +++ b/tests/api/routes/test_projects.py @@ -70,7 +70,7 @@ def test_post_new_projects_bad_request(client: TestClient, mocker: MockFixture) # then assert response.status_code == 400 assert response.headers["content-type"] == "text/html; charset=utf-8" - assert b"name: Field required" in response.content + assert b"Field required" in response.content def test_post_new_projects(client: TestClient, mocker: MockFixture) -> None: