1
0
Fork 0
mirror of https://gitlab.com/bramw/baserow.git synced 2025-04-10 23:50:12 +00:00

Merge branch 'develop'

This commit is contained in:
Bram Wiepjes 2021-01-13 13:17:37 +01:00
commit ab62f98ecb
14 changed files with 217 additions and 21 deletions
README.md
backend
setup.py
src/baserow
config/settings
contrib/database
tests/baserow/contrib/database/field
changelog.md
web-frontend

View file

@ -109,7 +109,7 @@ Created by Bram Wiepjes (Baserow) - bram@baserow.io.
Distributes under the MIT license. See `LICENSE` for more information.
Version: 0.7.0
Version: 0.7.1
The official repository can be found at https://gitlab.com/bramw/baserow.

View file

@ -6,7 +6,7 @@ from setuptools import find_packages, setup
PROJECT_DIR = os.path.dirname(__file__)
REQUIREMENTS_DIR = os.path.join(PROJECT_DIR, 'requirements')
VERSION = '0.7.0'
VERSION = '0.7.1'
def get_requirements(env):

View file

@ -153,7 +153,7 @@ SPECTACULAR_SETTINGS = {
'name': 'MIT',
'url': 'https://gitlab.com/bramw/baserow/-/blob/master/LICENSE'
},
'VERSION': '0.7.0',
'VERSION': '0.7.1',
'SERVE_INCLUDE_SCHEMA': False,
'TAGS': [
{'name': 'User'},

View file

@ -1,4 +1,5 @@
from django.utils.functional import lazy
from django.db import models
from drf_spectacular.utils import extend_schema_field
from drf_spectacular.types import OpenApiTypes
@ -66,6 +67,21 @@ class UpdateFieldSerializer(serializers.ModelSerializer):
}
class LinkRowListSerializer(serializers.ListSerializer):
def to_representation(self, data):
"""
Data that is fetched is always from another Table model and when fetching
that data we always need to respect the field enhancements. Otherwise it
could for example fail when we want to fetch the related select options that
could be in another database and table.
"""
if isinstance(data, models.Manager):
data = data.all().enhance_by_fields()
return super().to_representation(data)
class LinkRowValueSerializer(serializers.Serializer):
id = serializers.IntegerField(help_text='The unique identifier of the row in the '
'related table.')

View file

@ -118,7 +118,10 @@ def lenient_schema_editor(connection, alter_column_prepare_value=None,
if alert_column_type_function:
kwargs['alert_column_type_function'] = alert_column_type_function
with connection.schema_editor(**kwargs) as schema_editor:
yield schema_editor
connection.SchemaEditorClass = regular_schema_editor
try:
with connection.schema_editor(**kwargs) as schema_editor:
yield schema_editor
except Exception as e:
raise e
finally:
connection.SchemaEditorClass = regular_schema_editor

View file

@ -17,8 +17,8 @@ from rest_framework import serializers
from baserow.core.models import UserFile
from baserow.core.user_files.exceptions import UserFileDoesNotExist
from baserow.contrib.database.api.fields.serializers import (
LinkRowValueSerializer, FileFieldRequestSerializer, FileFieldResponseSerializer,
SelectOptionSerializer
LinkRowListSerializer, LinkRowValueSerializer, FileFieldRequestSerializer,
FileFieldResponseSerializer, SelectOptionSerializer
)
from baserow.contrib.database.api.fields.errors import (
ERROR_LINK_ROW_TABLE_NOT_IN_SAME_DATABASE, ERROR_LINK_ROW_TABLE_NOT_PROVIDED,
@ -348,8 +348,9 @@ class LinkRowFieldType(FieldType):
if primary_field:
primary_field_name = primary_field['name']
return LinkRowValueSerializer(many=True, value_field_name=primary_field_name,
required=False, **kwargs)
return LinkRowListSerializer(child=LinkRowValueSerializer(
value_field_name=primary_field_name, required=False, **kwargs
))
def get_serializer_help_text(self, instance):
return 'This field accepts an `array` containing the ids of the related rows.' \
@ -828,6 +829,11 @@ class SingleSelectFieldType(FieldType):
f"(lower(%({variable_name})s), '{int(option.id)}')"
)
# If there is no values we don't need to convert the value since all
# values should be converted to null.
if len(values_mapping) == 0:
return None
return f"""(
SELECT value FROM (
VALUES {','.join(values_mapping)}

View file

@ -236,14 +236,14 @@ class FieldHandler:
try:
schema_editor.alter_field(from_model, from_model_field,
to_model_field)
except (ProgrammingError, DataError):
except (ProgrammingError, DataError) as e:
# If something is going wrong while changing the schema we will
# just raise a specific exception. In the future we want to have
# some sort of converter abstraction where the values of certain
# types can be converted to another value.
logger.error(str(e))
message = f'Could not alter field when changing field type ' \
f'{from_field_type} to {new_type_name}.'
logger.error(message)
raise CannotChangeFieldType(message)
from_model_field_type = from_model_field.db_parameters(connection)['type']

View file

@ -118,6 +118,9 @@ class SelectOption(models.Model):
class Meta:
ordering = ('order', 'id',)
def __str__(self):
return self.value
class TextField(Field):
text_default = models.CharField(

View file

@ -1,14 +1,17 @@
import pytest
from decimal import Decimal
from unittest.mock import patch
from baserow.core.exceptions import UserNotInGroupError
from baserow.contrib.database.fields.handler import FieldHandler
from baserow.contrib.database.fields.models import (
Field, TextField, NumberField, BooleanField, SelectOption
)
from baserow.contrib.database.fields.field_types import TextFieldType
from baserow.contrib.database.fields.registries import field_type_registry
from baserow.contrib.database.fields.exceptions import (
FieldTypeDoesNotExist, PrimaryFieldAlreadyExists, CannotDeletePrimaryField,
FieldDoesNotExist, IncompatiblePrimaryFieldTypeError
FieldDoesNotExist, IncompatiblePrimaryFieldTypeError, CannotChangeFieldType
)
@ -144,10 +147,6 @@ def test_create_primary_field(data_fixture):
@pytest.mark.django_db
def test_update_field(data_fixture):
"""
@TODO somehow trigger the CannotChangeFieldType and test if it is raised.
"""
user = data_fixture.create_user()
user_2 = data_fixture.create_user()
table = data_fixture.create_database_table(user=user)
@ -237,6 +236,32 @@ def test_update_field(data_fixture):
assert getattr(rows[2], f'field_{field.id}') is False
@pytest.mark.django_db
def test_update_field_failing(data_fixture):
# This failing field type triggers the CannotChangeFieldType error if a field is
# changed into this type.
class FailingFieldType(TextFieldType):
def get_alter_column_type_function(self, connection, from_field, to_field):
return 'p_in::NOT_VALID_SQL_SO_IT_WILL_FAIL('
user = data_fixture.create_user()
table = data_fixture.create_database_table(user=user)
field = data_fixture.create_number_field(table=table, order=1)
handler = FieldHandler()
with patch.dict(
field_type_registry.registry,
{'text': FailingFieldType()}
):
with pytest.raises(CannotChangeFieldType):
handler.update_field(user=user, field=field, new_type_name='text')
handler.update_field(user, field=field, new_type_name='text')
assert Field.objects.all().count() == 1
assert TextField.objects.all().count() == 1
@pytest.mark.django_db
def test_delete_field(data_fixture):
user = data_fixture.create_user()

View file

@ -174,6 +174,25 @@ def test_single_select_field_type_rows(data_fixture, django_assert_num_queries):
assert getattr(row_4, f'field_{field.id}') is None
assert getattr(row_4, f'field_{field.id}_id') is None
field = field_handler.update_field(user=user, field=field, new_type_name='text')
assert field.select_options.all().count() == 0
model = table.get_model()
rows = model.objects.all().enhance_by_fields()
assert getattr(rows[0], f'field_{field.id}') is None
assert getattr(rows[1], f'field_{field.id}') == 'option 3'
assert getattr(rows[2], f'field_{field.id}') is None
assert getattr(rows[3], f'field_{field.id}') is None
field = field_handler.update_field(user=user, field=field,
new_type_name='single_select')
assert field.select_options.all().count() == 0
model = table.get_model()
rows = model.objects.all().enhance_by_fields()
assert getattr(rows[0], f'field_{field.id}') is None
assert getattr(rows[1], f'field_{field.id}') is None
assert getattr(rows[2], f'field_{field.id}') is None
assert getattr(rows[3], f'field_{field.id}') is None
@pytest.mark.django_db
def test_single_select_field_type_api_views(api_client, data_fixture):
@ -473,3 +492,80 @@ def test_single_select_field_type_get_order(data_fixture):
rows = view_handler.apply_sorting(grid_view, model.objects.all())
row_ids = [row.id for row in rows]
assert row_ids == [row_5.id, row_1.id, row_4.id, row_3.id, row_2.id]
@pytest.mark.django_db
def test_primary_single_select_field_with_link_row_field(api_client, data_fixture):
"""
We expect the relation to a table that has a single select field to work.
"""
user, token = data_fixture.create_user_and_token()
database = data_fixture.create_database_application(user=user, name='Placeholder')
example_table = data_fixture.create_database_table(name='Example',
database=database)
customers_table = data_fixture.create_database_table(name='Customers',
database=database)
field_handler = FieldHandler()
row_handler = RowHandler()
data_fixture.create_text_field(
name='Name',
table=example_table,
primary=True
)
customers_primary = field_handler.create_field(
user=user,
table=customers_table,
type_name='single_select',
select_options=[
{'value': 'Option 1', 'color': 'red'},
{'value': 'Option 2', 'color': 'blue'}
],
primary=True
)
link_row_field = field_handler.create_field(
user=user,
table=example_table,
type_name='link_row',
link_row_table=customers_table
)
select_options = customers_primary.select_options.all()
customers_row_1 = row_handler.create_row(
user=user, table=customers_table,
values={f'field_{customers_primary.id}': select_options[0].id}
)
customers_row_2 = row_handler.create_row(
user=user, table=customers_table,
values={f'field_{customers_primary.id}': select_options[1].id}
)
row_handler.create_row(
user, table=example_table,
values={f'field_{link_row_field.id}': [customers_row_1.id, customers_row_2.id]}
)
row_handler.create_row(
user, table=example_table,
values={f'field_{link_row_field.id}': [customers_row_1.id]}
)
response = api_client.get(
reverse('api:database:rows:list', kwargs={'table_id': example_table.id}),
format='json',
HTTP_AUTHORIZATION=f'JWT {token}'
)
response_json = response.json()
assert (
response_json['results'][0][f'field_{link_row_field.id}'][0]['value'] ==
'Option 1'
)
assert (
response_json['results'][0][f'field_{link_row_field.id}'][1]['value'] ==
'Option 2'
)
assert (
response_json['results'][1][f'field_{link_row_field.id}'][0]['value'] ==
'Option 1'
)

View file

@ -1,5 +1,12 @@
# Changelog
## Unreleased
* Fixed bug where you could not convert an existing field to a single select field
without select options.
* Fixed bug where is was not possible to create a relation to a table that has a single
select as primary field.
## Released (2021-01-06)
* Allow larger values for the number field and improved the validation.

View file

@ -33,6 +33,10 @@ import RowEditFieldFile from '@baserow/modules/database/components/row/RowEditFi
import RowEditFieldSingleSelect from '@baserow/modules/database/components/row/RowEditFieldSingleSelect'
import { trueString } from '@baserow/modules/database/utils/constants'
import {
getDateMomentFormat,
getTimeMomentFormat,
} from '@baserow/modules/database/utils/date'
export class FieldType extends Registerable {
/**
@ -151,7 +155,11 @@ export class FieldType extends Registerable {
}
/**
* Should return a for humans readable representation of the value.
* Should return a for humans readable representation of the value. This is for
* example used by the link row field and row modal. This is not a problem with most
* fields like text or number, but some store a more complex object object like
* the single select or file field. In this case, the object might needs to be
* converted to string.
*/
toHumanReadableString(field, value) {
return value
@ -661,6 +669,24 @@ export class DateFieldType extends FieldType {
}
}
toHumanReadableString(field, value) {
const date = moment.utc(value)
if (date.isValid()) {
const dateFormat = getDateMomentFormat(field.date_format)
let dateString = date.format(dateFormat)
if (field.date_include_time) {
const timeFormat = getTimeMomentFormat(field.date_time_format)
dateString = `${dateString} ${date.format(timeFormat)}`
}
return dateString
} else {
return ''
}
}
/**
* Tries to parse the clipboard text value with moment and returns the date in the
* correct format for the field. If it can't be parsed null is returned.
@ -812,6 +838,10 @@ export class FileFieldType extends FieldType {
return RowEditFieldFile
}
toHumanReadableString(field, value) {
return value.map((file) => file.visible_name).join(', ')
}
prepareValueForCopy(field, value) {
return JSON.stringify(value)
}
@ -955,6 +985,13 @@ export class SingleSelectFieldType extends FieldType {
}
}
toHumanReadableString(field, value) {
if (value === undefined || value === null) {
return ''
}
return value.value
}
getDocsDataType() {
return 'integer'
}

View file

@ -27,9 +27,12 @@ export default {
// Prepare the new value with all the relations and emit that value to the
// parent.
const newValue = JSON.parse(JSON.stringify(value))
const rowValue = this.$registry
.get('field', primary.type)
.toHumanReadableString(primary, row[`field_${primary.id}`])
newValue.push({
id: row.id,
value: row[`field_${primary.id}`].toString(),
value: rowValue.toString(),
})
this.$emit('update', newValue, value)
},

View file

@ -1,6 +1,6 @@
{
"name": "baserow",
"version": "0.7.0",
"version": "0.7.1",
"private": true,
"description": "Baserow: open source online database web frontend.",
"author": "Bram Wiepjes (Baserow)",