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:
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
|
@ -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":
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
}
|
|
@ -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",
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Add table
Reference in a new issue