1
0
Fork 0
mirror of https://gitlab.com/bramw/baserow.git synced 2025-03-15 04:54:50 +00:00

Merge branch '92-large-integers-result-in-error' into 'develop'

Resolve "Large integers result in an error"

Closes 

See merge request 
This commit is contained in:
Bram Wiepjes 2020-12-22 09:30:09 +00:00
commit cdc27f3b7e
16 changed files with 357 additions and 56 deletions

View file

@ -112,38 +112,44 @@ class NumberFieldType(FieldType):
serializer_field_names = ['number_type', 'number_decimal_places', 'number_negative']
def prepare_value_for_db(self, instance, value):
if value and instance.number_type == NUMBER_TYPE_DECIMAL:
if value is not None:
value = Decimal(value)
if value and not instance.number_negative and value < 0:
if value is not None and not instance.number_negative and value < 0:
raise ValidationError(f'The value for field {instance.id} cannot be '
f'negative.')
return value
def get_serializer_field(self, instance, **kwargs):
kwargs['required'] = False
kwargs['allow_null'] = True
kwargs['decimal_places'] = (
0
if instance.number_type == NUMBER_TYPE_INTEGER else
instance.number_decimal_places
)
if not instance.number_negative:
kwargs['min_value'] = 0
if instance.number_type == NUMBER_TYPE_INTEGER:
return serializers.IntegerField(**kwargs)
elif instance.number_type == NUMBER_TYPE_DECIMAL:
return serializers.DecimalField(
decimal_places=instance.number_decimal_places,
max_digits=self.MAX_DIGITS + instance.number_decimal_places,
**kwargs
)
return serializers.DecimalField(
max_digits=self.MAX_DIGITS + kwargs['decimal_places'],
required=False,
allow_null=True,
**kwargs
)
def get_model_field(self, instance, **kwargs):
kwargs['null'] = True
kwargs['blank'] = True
if instance.number_type == NUMBER_TYPE_INTEGER:
return models.IntegerField(**kwargs)
elif instance.number_type == NUMBER_TYPE_DECIMAL:
return models.DecimalField(
decimal_places=instance.number_decimal_places,
max_digits=self.MAX_DIGITS + instance.number_decimal_places,
**kwargs
)
kwargs['decimal_places'] = (
0
if instance.number_type == NUMBER_TYPE_INTEGER else
instance.number_decimal_places
)
return models.DecimalField(
max_digits=self.MAX_DIGITS + kwargs['decimal_places'],
null=True,
blank=True,
**kwargs
)
def random_value(self, instance, fake, cache):
if instance.number_type == NUMBER_TYPE_INTEGER:

View file

@ -0,0 +1,45 @@
# We need to change all NumberFields that are Integers to use DecimalField in Django
# and NUMERIC(50, 0) in Postgres. This migration converts all the existing Integer data
# types in fields to Decimal.
from baserow.contrib.database.fields.models import NUMBER_TYPE_INTEGER
from django.conf import settings
from django.db import migrations, connections
from baserow.contrib.database.fields.models import Field as FieldModel
def alter_sql(schema_editor, table_name, column_name):
changes_sql = schema_editor.sql_alter_column_type % {
"column": schema_editor.quote_name(column_name),
"type": 'NUMERIC(50,0)',
}
return schema_editor.sql_alter_column % {
"table": schema_editor.quote_name(table_name),
"changes": changes_sql,
}
def forward(apps, schema_editor):
NumberField = apps.get_model('database', 'NumberField')
connection = connections[settings.USER_TABLE_DATABASE]
with connection.schema_editor() as tables_schema_editor:
# We need to stop the transaction because we might need to lock a lot of tables
# which could result in an out of memory exception.
tables_schema_editor.atomic.__exit__(None, None, None)
for field in NumberField.objects.filter(number_type=NUMBER_TYPE_INTEGER):
table_name = f'database_table_{field.table.id}'
column_name = FieldModel.db_column.__get__(field, FieldModel)
sql = alter_sql(tables_schema_editor, table_name, column_name)
tables_schema_editor.execute(sql)
class Migration(migrations.Migration):
dependencies = [
('database', '0022_row_order'),
]
operations = [
migrations.RunPython(forward, migrations.RunPython.noop),
]

View file

@ -1,4 +1,5 @@
import re
from decimal import Decimal, DecimalException
from django.db import models
from django.db.models import Q
@ -51,8 +52,12 @@ class TableModelQuerySet(models.QuerySet):
"""
search_queries = models.Q()
excluded = ('order', 'created_on', 'updated_on')
for field in self.model._meta.get_fields():
if field.name in excluded:
continue
if (
isinstance(field, models.CharField) or
isinstance(field, models.TextField)
@ -70,6 +75,13 @@ class TableModelQuerySet(models.QuerySet):
})
except ValueError:
pass
elif isinstance(field, models.DecimalField):
try:
search_queries = search_queries | models.Q(**{
f'{field.name}': Decimal(search)
})
except (ValueError, DecimalException):
pass
return self.filter(search_queries) if len(search_queries) > 0 else self

View file

@ -3,13 +3,14 @@ from faker import Faker
from pytz import timezone
from datetime import date, datetime
from freezegun import freeze_time
from decimal import Decimal
from rest_framework.status import HTTP_200_OK, HTTP_204_NO_CONTENT, HTTP_400_BAD_REQUEST
from django.shortcuts import reverse
from baserow.contrib.database.fields.models import (
LongTextField, URLField, DateField, EmailField, FileField
LongTextField, URLField, DateField, EmailField, FileField, NumberField
)
@ -643,3 +644,201 @@ def test_file_field_type(api_client, data_fixture):
assert (
response_json['results'][2][f'field_{field_id}'][1]['name'] == user_file_2.name
)
@pytest.mark.django_db
def test_number_field_type(api_client, data_fixture):
user, token = data_fixture.create_user_and_token(
email='test@test.nl', password='password', first_name='Test1')
table = data_fixture.create_database_table(user=user)
# Create a positive integer field
response = api_client.post(
reverse('api:database:fields:list', kwargs={'table_id': table.id}),
{
'name': 'PositiveInt',
'type': 'number',
'number_type': 'INTEGER',
'number_negative': False,
},
format='json',
HTTP_AUTHORIZATION=f'JWT {token}'
)
# Make sure the field was created properly
response_json = response.json()
assert response.status_code == HTTP_200_OK
assert response_json['type'] == 'number'
assert NumberField.objects.all().count() == 1
positive_int_field_id = response_json['id']
# Create a negative integer field
response = api_client.post(
reverse('api:database:fields:list', kwargs={'table_id': table.id}),
{
'name': 'NegativeInt',
'type': 'number',
'number_type': 'INTEGER',
'number_negative': True,
},
format='json',
HTTP_AUTHORIZATION=f'JWT {token}'
)
# Make sure the field was created properly
response_json = response.json()
assert response.status_code == HTTP_200_OK
assert response_json['type'] == 'number'
assert NumberField.objects.all().count() == 2
negative_int_field_id = response_json['id']
# Create a positive decimal field
response = api_client.post(
reverse('api:database:fields:list', kwargs={'table_id': table.id}),
{
'name': 'PositiveDecimal',
'type': 'number',
'number_type': 'DECIMAL',
'number_negative': False,
'number_decimal_places': 2,
},
format='json',
HTTP_AUTHORIZATION=f'JWT {token}'
)
# Make sure the field was created properly
response_json = response.json()
assert response.status_code == HTTP_200_OK
assert response_json['type'] == 'number'
assert NumberField.objects.all().count() == 3
positive_decimal_field_id = response_json['id']
# Create a negative decimal field
response = api_client.post(
reverse('api:database:fields:list', kwargs={'table_id': table.id}),
{
'name': 'NegativeDecimal',
'type': 'number',
'number_type': 'DECIMAL',
'number_negative': True,
'number_decimal_places': 2,
},
format='json',
HTTP_AUTHORIZATION=f'JWT {token}'
)
# Make sure the field was created properly
response_json = response.json()
assert response.status_code == HTTP_200_OK
assert response_json['type'] == 'number'
assert NumberField.objects.all().count() == 4
negative_decimal_field_id = response_json['id']
# Test re-writing the name of a field. 'PositiveInt' is now called 'PositiveIntEdit'
response = api_client.patch(
reverse('api:database:fields:item', kwargs={'field_id': positive_int_field_id}),
{'name': 'PositiveIntEdit'},
format='json',
HTTP_AUTHORIZATION=f'JWT {token}'
)
assert response.status_code == HTTP_200_OK
# Add a row with correct values
response = api_client.post(
reverse('api:database:rows:list', kwargs={'table_id': table.id}),
{
f'field_{positive_int_field_id}':
'99999999999999999999999999999999999999999999999999',
f'field_{negative_int_field_id}':
'-99999999999999999999999999999999999999999999999999',
f'field_{positive_decimal_field_id}': 1000.00,
f'field_{negative_decimal_field_id}': -1000.00,
},
format='json',
HTTP_AUTHORIZATION=f'JWT {token}'
)
response_json = response.json()
assert response.status_code == HTTP_200_OK
assert (
response_json[f'field_{positive_int_field_id}'] ==
'99999999999999999999999999999999999999999999999999'
)
assert (
response_json[f'field_{negative_int_field_id}'] ==
'-99999999999999999999999999999999999999999999999999'
)
assert response_json[f'field_{positive_decimal_field_id}'] == '1000.00'
assert response_json[f'field_{negative_decimal_field_id}'] == '-1000.00'
model = table.get_model(attribute_names=True)
row = model.objects.all().last()
assert (
row.positiveintedit ==
Decimal('99999999999999999999999999999999999999999999999999')
)
assert (
row.negativeint ==
Decimal('-99999999999999999999999999999999999999999999999999')
)
assert row.positivedecimal == Decimal(1000.00)
assert row.negativedecimal == Decimal(-1000.00)
# Add a row with Nones'
response = api_client.post(
reverse('api:database:rows:list', kwargs={'table_id': table.id}),
{
f'field_{positive_int_field_id}': None,
f'field_{negative_int_field_id}': None,
f'field_{positive_decimal_field_id}': None,
f'field_{negative_decimal_field_id}': None,
},
format='json',
HTTP_AUTHORIZATION=f'JWT {token}'
)
response_json = response.json()
assert response.status_code == HTTP_200_OK
assert response_json[f'field_{positive_int_field_id}'] is None
assert response_json[f'field_{negative_int_field_id}'] is None
assert response_json[f'field_{positive_decimal_field_id}'] is None
assert response_json[f'field_{negative_decimal_field_id}'] is None
row = model.objects.all().last()
assert row.positiveintedit is None
assert row.negativeint is None
assert row.positivedecimal is None
assert row.negativedecimal is None
# Add a row with an integer that's too big
response = api_client.post(
reverse('api:database:rows:list', kwargs={'table_id': table.id}),
{
f'field_{positive_int_field_id}':
'999999999999999999999999999999999999999999999999999',
},
format='json',
HTTP_AUTHORIZATION=f'JWT {token}'
)
response_json = response.json()
assert response.status_code == HTTP_400_BAD_REQUEST
assert response_json['error'] == 'ERROR_REQUEST_BODY_VALIDATION'
assert (
response_json['detail'][f'field_{positive_int_field_id}'][0]['code'] ==
'max_digits'
)
# Add a row with an integer that's too small
response = api_client.post(
reverse('api:database:rows:list', kwargs={'table_id': table.id}),
{
f'field_{negative_int_field_id}':
'-9999999999999999999999999999999999999999999999999999',
},
format='json',
HTTP_AUTHORIZATION=f'JWT {token}'
)
assert response.status_code == HTTP_400_BAD_REQUEST
assert response_json['error'] == 'ERROR_REQUEST_BODY_VALIDATION'
assert (
response_json['detail'][f'field_{positive_int_field_id}'][0]['code'] ==
'max_digits'
)

View file

@ -15,8 +15,8 @@ def test_get_table_serializer(data_fixture):
data_fixture.create_text_field(table=table, order=0, name='Color',
text_default='white')
data_fixture.create_number_field(table=table, order=1, name='Horsepower')
data_fixture.create_boolean_field(table=table, order=2, name='For sale')
data_fixture.create_number_field(table=table, order=2, name='Price',
data_fixture.create_boolean_field(table=table, order=3, name='For sale')
data_fixture.create_number_field(table=table, order=4, name='Price',
number_type='DECIMAL', number_negative=True,
number_decimal_places=2)
@ -49,7 +49,21 @@ def test_get_table_serializer(data_fixture):
# number field
serializer_instance = serializer_class(data={'horsepower': 120})
assert serializer_instance.is_valid()
assert serializer_instance.data['horsepower'] == 120
assert serializer_instance.data['horsepower'] == '120'
serializer_instance = serializer_class(data={
'horsepower': 99999999999999999999999999999999999999999999999999
})
assert serializer_instance.is_valid()
assert (
serializer_instance.data['horsepower'] ==
'99999999999999999999999999999999999999999999999999'
)
serializer_instance = serializer_class(data={
'horsepower': 999999999999999999999999999999999999999999999999999
})
assert not serializer_instance.is_valid()
serializer_instance = serializer_class(data={'horsepower': None})
assert serializer_instance.is_valid()
@ -117,7 +131,7 @@ def test_get_table_serializer(data_fixture):
assert serializer_instance.is_valid()
assert serializer_instance.data == {
'color': 'green',
'horsepower': 120,
'horsepower': '120',
'for_sale': True,
'price': '120.22'
}

View file

@ -83,7 +83,7 @@ def test_list_rows(api_client, data_fixture):
assert len(response_json['results']) == 4
assert response_json['results'][0]['id'] == row_1.id
assert response_json['results'][0][f'field_{field_1.id}'] == 'Product 1'
assert response_json['results'][0][f'field_{field_2.id}'] == 50
assert response_json['results'][0][f'field_{field_2.id}'] == '50'
assert response_json['results'][0]['order'] == '1.00000000000000000000'
url = reverse('api:database:rows:list', kwargs={'table_id': table.id})
@ -482,7 +482,7 @@ def test_create_row(api_client, data_fixture):
response_json_row_3 = response.json()
assert response.status_code == HTTP_200_OK
assert response_json_row_3[f'field_{text_field.id}'] == 'Green'
assert response_json_row_3[f'field_{number_field.id}'] == 120
assert response_json_row_3[f'field_{number_field.id}'] == '120'
assert response_json_row_3[f'field_{boolean_field.id}']
assert response_json_row_3[f'field_{text_field_2.id}'] == 'Not important'
assert response_json_row_3['order'] == '3.00000000000000000000'
@ -501,7 +501,7 @@ def test_create_row(api_client, data_fixture):
response_json_row_4 = response.json()
assert response.status_code == HTTP_200_OK
assert response_json_row_4[f'field_{text_field.id}'] == 'Purple'
assert response_json_row_4[f'field_{number_field.id}'] == 240
assert response_json_row_4[f'field_{number_field.id}'] == '240'
assert response_json_row_4[f'field_{boolean_field.id}']
assert response_json_row_4[f'field_{text_field_2.id}'] == ''
assert response_json_row_4['order'] == '4.00000000000000000000'
@ -521,7 +521,7 @@ def test_create_row(api_client, data_fixture):
response_json_row_5 = response.json()
assert response.status_code == HTTP_200_OK
assert response_json_row_5[f'field_{text_field.id}'] == 'Red'
assert response_json_row_5[f'field_{number_field.id}'] == 480
assert response_json_row_5[f'field_{number_field.id}'] == '480'
assert not response_json_row_5[f'field_{boolean_field.id}']
assert response_json_row_5[f'field_{text_field_2.id}'] == ''
assert response_json_row_5['order'] == '2.99999999999999999999'
@ -669,7 +669,7 @@ def test_get_row(api_client, data_fixture):
assert response.status_code == HTTP_200_OK
assert response_json['id'] == row_1.id
assert response_json[f'field_{text_field.id}'] == 'Green'
assert response_json[f'field_{number_field.id}'] == 120
assert response_json[f'field_{number_field.id}'] == '120'
assert response_json[f'field_{boolean_field.id}'] is False
url = reverse('api:database:rows:item', kwargs={
@ -685,7 +685,7 @@ def test_get_row(api_client, data_fixture):
assert response.status_code == HTTP_200_OK
assert response_json['id'] == row_2.id
assert response_json[f'field_{text_field.id}'] == 'Purple'
assert response_json[f'field_{number_field.id}'] == 240
assert response_json[f'field_{number_field.id}'] == '240'
assert response_json[f'field_{boolean_field.id}'] is True
@ -814,12 +814,12 @@ def test_update_row(api_client, data_fixture):
assert response.status_code == HTTP_200_OK
assert response_json_row_1['id'] == row_1.id
assert response_json_row_1[f'field_{text_field.id}'] == 'Green'
assert response_json_row_1[f'field_{number_field.id}'] == 120
assert response_json_row_1[f'field_{number_field.id}'] == '120'
assert response_json_row_1[f'field_{boolean_field.id}'] is True
row_1.refresh_from_db()
assert getattr(row_1, f'field_{text_field.id}') == 'Green'
assert getattr(row_1, f'field_{number_field.id}') == 120
assert getattr(row_1, f'field_{number_field.id}') == Decimal('120')
assert getattr(row_1, f'field_{boolean_field.id}') is True
response = api_client.patch(
@ -871,12 +871,12 @@ def test_update_row(api_client, data_fixture):
assert response.status_code == HTTP_200_OK
assert response_json_row_2['id'] == row_2.id
assert response_json_row_2[f'field_{text_field.id}'] == 'Blue'
assert response_json_row_2[f'field_{number_field.id}'] == 50
assert response_json_row_2[f'field_{number_field.id}'] == '50'
assert response_json_row_2[f'field_{boolean_field.id}'] is False
row_2.refresh_from_db()
assert getattr(row_2, f'field_{text_field.id}') == 'Blue'
assert getattr(row_2, f'field_{number_field.id}') == 50
assert getattr(row_2, f'field_{number_field.id}') == Decimal('50')
assert getattr(row_2, f'field_{boolean_field.id}') is False
url = reverse('api:database:rows:item', kwargs={

View file

@ -66,7 +66,7 @@ def test_list_rows(api_client, data_fixture):
assert len(response_json['results']) == 4
assert response_json['results'][0]['id'] == row_1.id
assert response_json['results'][0][f'field_{text_field.id}'] == 'Green'
assert response_json['results'][0][f'field_{number_field.id}'] == 10
assert response_json['results'][0][f'field_{number_field.id}'] == '10'
assert not response_json['results'][0][f'field_{boolean_field.id}']
assert response_json['results'][1]['id'] == row_2.id
assert response_json['results'][2]['id'] == row_3.id

View file

@ -24,17 +24,22 @@ from baserow.contrib.database.rows.handler import RowHandler
"expected,field_kwargs",
[
(
[100, 100, 101, 0, 0, 0, None, None, None, None, None],
[
9223372036854775807, 100, 100, 101, 0, 0, 0, 0, None, None, None, None,
None
],
{'number_type': 'INTEGER', 'number_negative': False}
),
(
[100, 100, 101, -100, -100, -101, None, None, None, None, None],
[9223372036854775807, 100, 100, 101, -9223372036854775808, -100, -100, -101,
None, None, None, None, None],
{'number_type': 'INTEGER', 'number_negative': True}
),
(
[
Decimal('100.0'), Decimal('100.2'), Decimal('100.6'), Decimal('0.0'),
Decimal('0.0'), Decimal('0.0'), None, None, None, None, None
Decimal('9223372036854775807.0'), Decimal('100.0'), Decimal('100.2'),
Decimal('100.6'), Decimal('0.0'), Decimal('0.0'), Decimal('0.0'),
Decimal('0.0'), None, None, None, None, None
],
{
'number_type': 'DECIMAL', 'number_negative': False,
@ -43,9 +48,10 @@ from baserow.contrib.database.rows.handler import RowHandler
),
(
[
Decimal('100.000'), Decimal('100.220'), Decimal('100.600'),
Decimal('-100.0'), Decimal('-100.220'), Decimal('-100.600'), None, None,
None, None, None
Decimal('9223372036854775807.000'), Decimal('100.000'),
Decimal('100.220'), Decimal('100.600'),
Decimal('-9223372036854775808.0'), Decimal('-100.0'),
Decimal('-100.220'), Decimal('-100.600'), None, None, None, None, None
],
{
'number_type': 'DECIMAL', 'number_negative': True,
@ -63,9 +69,11 @@ def test_alter_number_field_column_type(expected, field_kwargs, data_fixture):
field = handler.update_field(user=user, field=field, name='Text field')
model = table.get_model()
model.objects.create(**{f'field_{field.id}': '9223372036854775807'})
model.objects.create(**{f'field_{field.id}': '100'})
model.objects.create(**{f'field_{field.id}': '100.22'})
model.objects.create(**{f'field_{field.id}': '100.59999'})
model.objects.create(**{f'field_{field.id}': '-9223372036854775808'})
model.objects.create(**{f'field_{field.id}': '-100'})
model.objects.create(**{f'field_{field.id}': '-100.22'})
model.objects.create(**{f'field_{field.id}': '-100.5999'})

View file

@ -53,7 +53,7 @@ def test_get_table_model(data_fixture):
assert color_field.default == 'white'
assert color_field.null
assert isinstance(horsepower_field, models.IntegerField)
assert isinstance(horsepower_field, models.DecimalField)
assert horsepower_field.verbose_name == 'Horsepower'
assert horsepower_field.db_column == f'field_{number_field.id}'
assert horsepower_field.null
@ -95,7 +95,7 @@ def test_get_table_model(data_fixture):
assert field_1.db_column == f'field_{text_field.id}'
field_2 = model_3._meta.get_field(f'field_{number_field.id}')
assert isinstance(field_2, models.IntegerField)
assert isinstance(field_2, models.DecimalField)
assert field_2.db_column == f'field_{number_field.id}'
field_3 = model_3._meta.get_field(f'field_{boolean_field.id}')

View file

@ -1,5 +1,6 @@
# Changelog
* Allow larger values for the number field and improved the validation.
* Fixed bug where if you have no filters, but the filter type is set to `OR` it always
results in a not matching row state in the web-frontend.
* Fixed bug where the arrow navigation didn't work for the dropdown component in

View file

@ -19,9 +19,9 @@
<script>
import rowEditField from '@baserow/modules/database/mixins/rowEditField'
import rowEditFieldInput from '@baserow/modules/database/mixins/rowEditFieldInput'
import EmailField from '@baserow/modules/database/mixins/EmailField'
import emailField from '@baserow/modules/database/mixins/emailField'
export default {
mixins: [rowEditField, rowEditFieldInput, EmailField],
mixins: [rowEditField, rowEditFieldInput, emailField],
}
</script>

View file

@ -31,10 +31,10 @@
<script>
import gridField from '@baserow/modules/database/mixins/gridField'
import gridFieldInput from '@baserow/modules/database/mixins/gridFieldInput'
import EmailField from '@baserow/modules/database/mixins/EmailField'
import emailField from '@baserow/modules/database/mixins/emailField'
export default {
mixins: [gridField, gridFieldInput, EmailField],
mixins: [gridField, gridFieldInput, emailField],
methods: {
afterEdit() {
this.$nextTick(() => {

View file

@ -1,4 +1,5 @@
import moment from 'moment'
import BigNumber from 'bignumber.js'
import { isValidURL, isValidEmail } from '@baserow/modules/core/utils/string'
import { Registerable } from '@baserow/modules/core/registry'
@ -446,6 +447,8 @@ export class LinkRowFieldType extends FieldType {
}
export class NumberFieldType extends FieldType {
static maxNumberLength = 50
static getType() {
return 'number'
}
@ -493,7 +496,12 @@ export class NumberFieldType extends FieldType {
*/
prepareValueForPaste(field, clipboardData) {
const value = clipboardData.getData('text')
if (isNaN(parseFloat(value)) || !isFinite(value)) {
if (
isNaN(parseFloat(value)) ||
!isFinite(value) ||
value.split('.')[0].replace('-', '').length >
NumberFieldType.maxNumberLength
) {
return null
}
return this.constructor.formatNumber(field, value)
@ -510,15 +518,15 @@ export class NumberFieldType extends FieldType {
}
const decimalPlaces =
field.number_type === 'DECIMAL' ? field.number_decimal_places : 0
let number = parseFloat(value)
if (!field.number_negative && number < 0) {
let number = new BigNumber(value)
if (!field.number_negative && number.isLessThan(0)) {
number = 0
}
return number.toFixed(decimalPlaces)
}
getDocsDataType(field) {
return field.number_type === 'DECIMAL' ? 'decimal' : 'integer'
return field.number_type === 'DECIMAL' ? 'decimal' : 'number'
}
getDocsDescription(field) {

View file

@ -74,8 +74,10 @@ export default {
* need it. We will also save the changes if the user was editing.
*/
beforeUnSelect() {
if (this.editing) {
if (this.editing && this.isValid()) {
this.save()
} else {
this.editing = false
}
document.body.removeEventListener('keydown', this.$el.keydownEvent)
},

View file

@ -17,6 +17,12 @@ export default {
if (isNaN(parseFloat(this.copy)) || !isFinite(this.copy)) {
return 'Invalid number'
}
if (
this.copy.split('.')[0].replace('-', '').length >
NumberFieldType.maxNumberLength
) {
return `Max ${NumberFieldType.maxNumberLength} digits allowed.`
}
return null
},
isValid() {