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

Resolve "Create a configurable field limit"

This commit is contained in:
Hassan K Salim 2021-05-05 22:09:28 +00:00 committed by Bram Wiepjes
parent ed34899093
commit ffa6e8a1ee
13 changed files with 111 additions and 1 deletions
backend
src/baserow
config/settings
contrib/database
tests/baserow/contrib/database
changelog.md
web-frontend/modules/core/plugins

View file

@ -265,3 +265,5 @@ APPLICATION_TEMPLATES_DIR = os.path.join(BASE_DIR, "../../../templates")
# The template that must be selected when the user first opens the templates select
# modal.
DEFAULT_APPLICATION_TEMPLATE = "project-management"
MAX_FIELD_LIMIT = 1500

View file

@ -40,3 +40,5 @@ ERROR_INCOMPATIBLE_PRIMARY_FIELD_TYPE = (
HTTP_400_BAD_REQUEST,
"The field type {e.field_type} is not compatible with the primary field.",
)
ERROR_MAX_FIELD_COUNT_EXCEEDED = "ERROR_MAX_FIELD_COUNT_EXCEEDED"

View file

@ -21,11 +21,13 @@ from baserow.contrib.database.api.fields.errors import (
ERROR_CANNOT_DELETE_PRIMARY_FIELD,
ERROR_CANNOT_CHANGE_FIELD_TYPE,
ERROR_FIELD_DOES_NOT_EXIST,
ERROR_MAX_FIELD_COUNT_EXCEEDED,
)
from baserow.contrib.database.fields.exceptions import (
CannotDeletePrimaryField,
CannotChangeFieldType,
FieldDoesNotExist,
MaxFieldLimitExceeded,
)
from baserow.contrib.database.fields.models import Field
from baserow.contrib.database.fields.handler import FieldHandler
@ -122,7 +124,11 @@ class FieldsView(APIView):
field_type_registry, FieldSerializer
),
400: get_error_schema(
["ERROR_USER_NOT_IN_GROUP", "ERROR_REQUEST_BODY_VALIDATION"]
[
"ERROR_USER_NOT_IN_GROUP",
"ERROR_REQUEST_BODY_VALIDATION",
"ERROR_MAX_FIELD_COUNT_EXCEEDED",
]
),
404: get_error_schema(["ERROR_TABLE_DOES_NOT_EXIST"]),
},
@ -135,6 +141,7 @@ class FieldsView(APIView):
{
TableDoesNotExist: ERROR_TABLE_DOES_NOT_EXIST,
UserNotInGroup: ERROR_USER_NOT_IN_GROUP,
MaxFieldLimitExceeded: ERROR_MAX_FIELD_COUNT_EXCEEDED,
}
)
def post(self, request, data, table_id):

View file

@ -13,6 +13,8 @@ from baserow.api.schemas import get_error_schema
from baserow.api.applications.errors import ERROR_APPLICATION_DOES_NOT_EXIST
from baserow.core.exceptions import UserNotInGroup, ApplicationDoesNotExist
from baserow.core.handler import CoreHandler
from baserow.contrib.database.api.fields.errors import ERROR_MAX_FIELD_COUNT_EXCEEDED
from baserow.contrib.database.fields.exceptions import MaxFieldLimitExceeded
from baserow.contrib.database.models import Database
from baserow.contrib.database.table.models import Table
from baserow.contrib.database.table.handler import TableHandler
@ -114,6 +116,7 @@ class TablesView(APIView):
UserNotInGroup: ERROR_USER_NOT_IN_GROUP,
InvalidInitialTableData: ERROR_INVALID_INITIAL_TABLE_DATA,
InitialTableDataLimitExceeded: ERROR_INITIAL_TABLE_DATA_LIMIT_EXCEEDED,
MaxFieldLimitExceeded: ERROR_MAX_FIELD_COUNT_EXCEEDED,
}
)
@validate_body(TableCreateSerializer)

View file

@ -45,6 +45,10 @@ class LinkRowTableNotInSameDatabase(Exception):
"""
class MaxFieldLimitExceeded(Exception):
""" Raised when the field count exceeds the limit"""
class OrderByFieldNotFound(Exception):
"""Raised when the field was not found in the table."""

View file

@ -14,6 +14,7 @@ from .exceptions import (
CannotChangeFieldType,
FieldDoesNotExist,
IncompatiblePrimaryFieldTypeError,
MaxFieldLimitExceeded,
)
from .models import Field, SelectOption
from .registries import field_type_registry, field_converter_registry
@ -80,6 +81,8 @@ class FieldHandler:
:type kwargs: object
:raises PrimaryFieldAlreadyExists: When we try to create a primary field,
but one already exists.
:raises MaxFieldLimitExceeded: When we try to create a field,
but exceeds the field limit.
:return: The created field instance.
:rtype: Field
"""
@ -102,6 +105,12 @@ class FieldHandler:
field_values = extract_allowed(kwargs, allowed_fields)
last_order = model_class.get_last_order(table)
num_fields = table.field_set.count()
if (num_fields + 1) > settings.MAX_FIELD_LIMIT:
raise MaxFieldLimitExceeded(
f"Fields count exceeds the limit of {settings.MAX_FIELD_LIMIT}"
)
field_values = field_type.prepare_values(field_values, user)
before = field_type.before_create(
table, primary, field_values, last_order, user

View file

@ -6,6 +6,7 @@ from baserow.contrib.database.fields.models import TextField
from baserow.contrib.database.views.handler import ViewHandler
from baserow.contrib.database.views.view_types import GridViewType
from baserow.contrib.database.fields.handler import FieldHandler
from baserow.contrib.database.fields.exceptions import MaxFieldLimitExceeded
from baserow.contrib.database.fields.field_types import (
LongTextFieldType,
BooleanFieldType,
@ -73,6 +74,8 @@ class TableHandler:
:type first_row_header: bool
:param kwargs: The fields that need to be set upon creation.
:type kwargs: object
:raises MaxFieldLimitExceeded: When the data contains more columns
than the field limit.
:return: The created table instance.
:rtype: Table
"""
@ -81,6 +84,10 @@ class TableHandler:
if data is not None:
fields, data = self.normalize_initial_table_data(data, first_row_header)
if len(fields) > settings.MAX_FIELD_LIMIT:
raise MaxFieldLimitExceeded(
f"Fields count exceeds the limit of {settings.MAX_FIELD_LIMIT}"
)
table_values = extract_allowed(kwargs, ["name"])
last_order = Table.get_last_order(database)

View file

@ -8,6 +8,7 @@ from rest_framework.status import (
)
from django.shortcuts import reverse
from django.conf import settings
from baserow.contrib.database.fields.models import Field, TextField, NumberField
@ -109,6 +110,18 @@ def test_create_field(api_client, data_fixture):
assert response.status_code == HTTP_400_BAD_REQUEST
assert response.json()["error"] == "ERROR_USER_NOT_IN_GROUP"
field_limit = settings.MAX_FIELD_LIMIT
settings.MAX_FIELD_LIMIT = 0
response = api_client.post(
reverse("api:database:fields:list", kwargs={"table_id": table.id}),
{"name": "Test 1", "type": "text"},
format="json",
HTTP_AUTHORIZATION=f"JWT {token}",
)
assert response.status_code == HTTP_400_BAD_REQUEST
assert response.json()["error"] == "ERROR_MAX_FIELD_COUNT_EXCEEDED"
settings.MAX_FIELD_LIMIT = field_limit
url = reverse("api:database:fields:list", kwargs={"table_id": table_2.id})
response = api_client.get(url)
assert response.status_code == HTTP_401_UNAUTHORIZED

View file

@ -122,6 +122,20 @@ def test_create_table_with_data(api_client, data_fixture):
assert response_json["error"] == "ERROR_INITIAL_TABLE_DATA_LIMIT_EXCEEDED"
settings.INITIAL_TABLE_DATA_LIMIT = limit
field_limit = settings.MAX_FIELD_LIMIT
settings.MAX_FIELD_LIMIT = 2
url = reverse("api:database:tables:list", kwargs={"database_id": database.id})
response = api_client.post(
url,
{"name": "Test 1", "data": [["fields"] * 3, ["rows"] * 3]},
format="json",
HTTP_AUTHORIZATION=f"JWT {token}",
)
response_json = response.json()
assert response.status_code == HTTP_400_BAD_REQUEST
assert response_json["error"] == "ERROR_MAX_FIELD_COUNT_EXCEEDED"
settings.MAX_FIELD_LIMIT = field_limit
url = reverse("api:database:tables:list", kwargs={"database_id": database.id})
response = api_client.post(
url,

View file

@ -3,6 +3,7 @@ from decimal import Decimal
from unittest.mock import patch
import pytest
from django.conf import settings
from django.db import models
from faker import Faker
@ -13,6 +14,7 @@ from baserow.contrib.database.fields.exceptions import (
FieldDoesNotExist,
IncompatiblePrimaryFieldTypeError,
CannotChangeFieldType,
MaxFieldLimitExceeded,
)
from baserow.contrib.database.fields.field_helpers import (
construct_all_possible_field_kwargs,
@ -224,6 +226,20 @@ def test_create_field(send_mock, data_fixture):
assert NumberField.objects.all().count() == 1
assert BooleanField.objects.all().count() == 1
field_limit = settings.MAX_FIELD_LIMIT
settings.MAX_FIELD_LIMIT = 2
with pytest.raises(MaxFieldLimitExceeded):
handler.create_field(
user=user,
table=table,
type_name="text",
name="Test text field",
text_default="Some default",
)
settings.MAX_FIELD_LIMIT = field_limit
with pytest.raises(UserNotInGroup):
handler.create_field(user=user_2, table=table, type_name="text")

View file

@ -6,6 +6,7 @@ from django.conf import settings
from decimal import Decimal
from baserow.core.exceptions import UserNotInGroup
from baserow.contrib.database.fields.exceptions import MaxFieldLimitExceeded
from baserow.contrib.database.table.models import Table
from baserow.contrib.database.table.handler import TableHandler
from baserow.contrib.database.table.exceptions import (
@ -131,6 +132,16 @@ def test_fill_table_with_initial_data(data_fixture):
settings.INITIAL_TABLE_DATA_LIMIT = limit
field_limit = settings.MAX_FIELD_LIMIT
settings.MAX_FIELD_LIMIT = 2
with pytest.raises(MaxFieldLimitExceeded):
table_handler.create_table(
user, database, name="Table 1", data=[["fields"] * 3, ["rows"] * 3]
)
settings.MAX_FIELD_LIMIT = field_limit
data = [
["A", "B", "C", "D"],
["1-1", "1-2", "1-3", "1-4", "1-5"],
@ -205,6 +216,22 @@ def test_fill_table_with_initial_data(data_fixture):
assert getattr(results[2], f"field_{text_fields[0].id}") == "3-1"
assert getattr(results[2], f"field_{text_fields[1].id}") == "3-2"
field_limit = settings.MAX_FIELD_LIMIT
settings.MAX_FIELD_LIMIT = 5
data = [
["A", "B", "C", "D", "E"],
["1-1", "1-2", "1-3", "1-4", "1-5"],
]
table = table_handler.create_table(
user, database, name="Table 3", data=data, first_row_header=True
)
num_fields = table.field_set.count()
assert GridView.objects.all().count() == 3
assert num_fields == settings.MAX_FIELD_LIMIT
settings.MAX_FIELD_LIMIT = field_limit
@pytest.mark.django_db
@patch("baserow.contrib.database.table.signals.table_updated.send")

View file

@ -1,7 +1,9 @@
# Changelog
## Unreleased
* Added configurable field limit.
* Fixed memory leak in the `link_row` field.
* Switch to using a celery based email backend by default.
* Added `--add-columns` flag to the `fill_table` management command. It creates all the

View file

@ -56,6 +56,10 @@ class ErrorHandler {
'Invalid URL',
'The provided file URL is invalid or not allowed.'
),
ERROR_MAX_FIELD_COUNT_EXCEEDED: new ResponseErrorMessage(
"Couldn't create field.",
"The action couldn't be completed because the field count exceeds the limit"
),
}
// A temporary notFoundMap containing the error messages for when the