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 #92 See merge request bramw/baserow!109
This commit is contained in:
commit
cdc27f3b7e
16 changed files with 357 additions and 56 deletions
backend
src/baserow/contrib/database
tests/baserow/contrib/database
api
fields
rows
views/grid
field
table
web-frontend/modules/database
|
@ -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:
|
||||
|
|
|
@ -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),
|
||||
]
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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'
|
||||
)
|
||||
|
|
|
@ -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'
|
||||
}
|
||||
|
|
|
@ -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={
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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'})
|
||||
|
|
|
@ -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}')
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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(() => {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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)
|
||||
},
|
||||
|
|
|
@ -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() {
|
||||
|
|
Loading…
Reference in a new issue