1
0
Fork 0
mirror of https://gitlab.com/bramw/baserow.git synced 2025-04-11 16:01:20 +00:00

fix: enhance input validation to support decimal numbers and improve error handling

This commit is contained in:
Evren Ozkan 2025-03-27 11:27:11 +01:00
parent 8d1b42a33b
commit 3aacc894f1
6 changed files with 60 additions and 49 deletions
backend
src/baserow/contrib/builder/elements
tests/baserow/contrib/builder/elements
changelog/entries/unreleased/bug
web-frontend
modules
builder/locales
core/utils
test/unit/core/utils

View file

@ -1,6 +1,7 @@
import abc
import uuid
from datetime import datetime
from decimal import Decimal, InvalidOperation
from typing import (
Any,
Callable,
@ -1452,7 +1453,7 @@ class InputTextElementType(InputElementType):
def is_valid(
self, element: InputTextElement, value: Any, dispatch_context: DispatchContext
) -> bool:
) -> Any:
"""
:param element: The element we're trying to use form data in.
:param value: The form data value, which may be invalid.
@ -1465,10 +1466,14 @@ class InputTextElementType(InputElementType):
elif element.validation_type == "integer":
try:
value = ensure_integer(value)
except ValidationError as exc:
# First try to convert any decimal separators to dots
if isinstance(value, str):
value = value.replace(",", ".")
return Decimal(value) if "." in value else ensure_integer(value)
return ensure_integer(value)
except (ValueError, TypeError, InvalidOperation, ValidationError) as exc:
raise FormDataProviderChunkInvalidException(
f"{value} must be a valid integer."
f"{value} must be a valid number."
) from exc
elif element.validation_type == "email":

View file

@ -1,5 +1,6 @@
import json
from collections import defaultdict
from decimal import Decimal
from io import BytesIO
from tempfile import tempdir
from unittest.mock import MagicMock, Mock
@ -506,48 +507,43 @@ def test_choice_element_import_export_formula(data_fixture):
@pytest.mark.django_db
def test_input_text_element_is_valid(data_fixture):
validity_tests = [
{"required": True, "type": "integer", "value": "", "result": False},
{"required": True, "type": "integer", "value": 42, "result": 42},
{"required": True, "type": "integer", "value": "42", "result": 42},
{"required": True, "type": "integer", "value": "horse", "result": False},
{"required": False, "type": "integer", "value": "", "result": ""},
{
"required": True,
"type": "email",
"value": "foo@bar.com",
"result": "foo@bar.com",
},
{"required": True, "type": "email", "value": "foobar.com", "result": False},
{"required": False, "type": "email", "value": "", "result": ""},
{"required": True, "type": "any", "value": "", "result": False},
{"required": True, "type": "any", "value": 42, "result": 42},
{"required": True, "type": "any", "value": "42", "result": "42"},
{"required": True, "type": "any", "value": "horse", "result": "horse"},
{"required": False, "type": "any", "value": "", "result": ""},
]
for test in validity_tests:
if test["result"] is not False:
assert (
InputTextElementType().is_valid(
InputTextElement(
validation_type=test["type"], required=test["required"]
),
test["value"],
{},
)
== test["result"]
), repr(test["value"])
else:
with pytest.raises(FormDataProviderChunkInvalidException):
InputTextElementType().is_valid(
InputTextElement(
validation_type=test["type"], required=test["required"]
),
test["value"],
{},
)
@pytest.mark.parametrize(
"required,type,value,result",
[
(True, "integer", "", False),
(True, "integer", 42, 42),
(True, "integer", "4.2", Decimal("4.2")),
(True, "integer", "4,2", Decimal("4.2")),
(True, "integer", "42", 42),
(True, "integer", "horse", False),
(False, "integer", "", ""),
(True, "email", "foo@bar.com", "foo@bar.com"),
(True, "email", "foobar.com", False),
(False, "email", "", ""),
(True, "any", "", False),
(True, "any", 42, 42),
(True, "any", "42", "42"),
(True, "any", "horse", "horse"),
(False, "any", "", ""),
],
)
def test_input_text_element_is_valid(data_fixture, required, type, value, result):
if result is not False:
assert (
InputTextElementType().is_valid(
InputTextElement(validation_type=type, required=required),
value,
{},
)
== result
), repr(f"{value} != {result}")
else:
with pytest.raises(FormDataProviderChunkInvalidException):
InputTextElementType().is_valid(
InputTextElement(validation_type=type, required=required),
value,
{},
)
@pytest.mark.django_db

View file

@ -0,0 +1,8 @@
{
"type": "bug",
"message": "Fix \"number\" validation for handling of decimal numbers with using comma or dot as decimal separator",
"domain": "builder",
"issue_number": 3408,
"bullet_points": [],
"created_at": "2025-03-27"
}

View file

@ -266,7 +266,7 @@
"validationTypeAnyLabel": "Any",
"validationTypeAnyDescription": "Allow any value to be set in this input.",
"validationTypeIntegerLabel": "Number",
"validationTypeIntegerDescription": "Enforce a number value in this input.",
"validationTypeIntegerDescription": "Enforce a numeric value in this input (accepts integers and decimals).",
"validationTypeEmailLabel": "Email",
"validationTypeEmailDescription": "Enforce an email address value in this input.",
"inputType": "Input type",

View file

@ -138,7 +138,8 @@ export const escapeRegExp = (string) => {
}
export const isNumeric = (value) => {
return /^-?\d+$/.test(value)
// Accept both integers and decimal numbers with dot or comma as separator
return /^-?\d+([.,]\d+)?$/.test(value)
}
/**

View file

@ -131,7 +131,8 @@ describe('test string utils', () => {
test('test isNumeric', () => {
expect(isNumeric('a')).toBe(false)
expect(isNumeric('1.2')).toBe(false)
expect(isNumeric('1.2')).toBe(true)
expect(isNumeric('1,2')).toBe(true)
expect(isNumeric('')).toBe(false)
expect(isNumeric('null')).toBe(false)
expect(isNumeric('12px')).toBe(false)