1
0
Fork 0
mirror of https://gitlab.com/bramw/baserow.git synced 2025-04-10 15:47:32 +00:00

Not possible to change field type with active sort

This commit is contained in:
Przemyslaw Kukulski 2025-03-18 15:08:05 +00:00
parent ece699f44f
commit adf3fc482a
4 changed files with 98 additions and 7 deletions
backend
src/baserow/contrib/database
tests/baserow/contrib/database/api/fields
changelog/entries/unreleased/bug

View file

@ -363,7 +363,7 @@ class FieldHandler(metaclass=baserow_trace_methods(tracer)):
# already exists. If so the field cannot be created and an exception is raised.
if primary and Field.objects.filter(table=table, primary=True).exists():
raise PrimaryFieldAlreadyExists(
f"A primary field already exists for the " f"table {table}."
f"A primary field already exists for the table {table}."
)
# Figure out which model to use and which field types are allowed for the given
@ -579,6 +579,7 @@ class FieldHandler(metaclass=baserow_trace_methods(tracer)):
raise IncompatiblePrimaryFieldTypeError(to_field_type_name)
if baserow_field_type_changed:
ViewHandler().before_field_type_change(field)
dependants_broken_due_to_type_change = (
from_field_type.get_dependants_which_will_break_when_field_type_changes(
field, to_field_type, field_cache

View file

@ -222,6 +222,41 @@ class ViewIndexingHandler(metaclass=baserow_trace_methods(tracer)):
return f"i{table_id}:"
@classmethod
def before_field_type_change(cls, field: Field, model=None):
"""
Remove all the indexes for the views that have a sort on the field
that is being changed.
:param field: The field that is being changed.
:param model: The model to use for the table. If not provided it will be
taken from the field.
"""
views = View.objects.filter(
id__in=ViewSort.objects.filter(field=field).values("view_id"),
db_index_name__isnull=False,
)
if not views:
return
if model is None:
model = field.table.get_model()
dropped_indexes = set()
for view in views:
if view.db_index_name in dropped_indexes:
continue
cls.drop_index(
view=view,
db_index=django_models.Index("id", name=view.db_index_name),
model=model,
)
dropped_indexes.add(view.db_index_name)
View.objects.filter(id__in=[v.id for v in views]).update(db_index_name=None)
@classmethod
def _get_index_hash(
cls, field_order_bys: List[OptionallyAnnotatedOrderBy]
@ -447,6 +482,12 @@ class ViewIndexingHandler(metaclass=baserow_trace_methods(tracer)):
):
return current_index_name
cls.drop_index(view, db_index, model)
return current_index_name
@classmethod
def drop_index(cls, view, db_index, model=None):
if model is None:
model = view.table.get_model()
@ -459,8 +500,6 @@ class ViewIndexingHandler(metaclass=baserow_trace_methods(tracer)):
view_table_id=view.table_id,
)
return current_index_name
@classmethod
def update_index_by_view_id(cls, view_id: int, nowait=True):
"""
@ -598,6 +637,16 @@ class ViewHandler(metaclass=baserow_trace_methods(tracer)):
)
return views
def before_field_type_change(self, field: Field):
"""
Allow trigger custom logic before field is changed.
By default it calls ViewIndexingHandler.before_field_type_change.
:param field: The field that is being changed.
"""
ViewIndexingHandler.before_field_type_change(field)
def list_workspace_views(
self,
user: AbstractUser,

View file

@ -15,6 +15,8 @@ from rest_framework.status import (
from baserow.contrib.database.fields.models import Field, NumberField, TextField
from baserow.contrib.database.fields.registries import field_type_registry
from baserow.contrib.database.tokens.handler import TokenHandler
from baserow.contrib.database.views.handler import ViewIndexingHandler
from baserow.contrib.database.views.models import ViewSort
from baserow.core.db import specific_iterator
from baserow.test_utils.helpers import (
independent_test_db_connection,
@ -519,7 +521,7 @@ def test_update_field(api_client, data_fixture):
url,
{"name": existing_field.name},
format="json",
HTTP_AUTHORIZATION=f"JWT" f" {token}",
HTTP_AUTHORIZATION=f"JWT {token}",
)
assert response.status_code == HTTP_400_BAD_REQUEST
assert response.json()["error"] == "ERROR_FIELD_WITH_SAME_NAME_ALREADY_EXISTS"
@ -530,7 +532,7 @@ def test_update_field(api_client, data_fixture):
url,
{"name": too_long_field_name},
format="json",
HTTP_AUTHORIZATION=f"JWT" f" {token}",
HTTP_AUTHORIZATION=f"JWT {token}",
)
assert response.status_code == HTTP_400_BAD_REQUEST
assert response.json()["error"] == "ERROR_REQUEST_BODY_VALIDATION"
@ -717,6 +719,38 @@ def test_update_field_number_type_deprecation_error(api_client, data_fixture):
)
@pytest.mark.django_db
def test_change_field_type_with_active_sort_on_field(api_client, data_fixture):
import uuid
user, token = data_fixture.create_user_and_token()
table = data_fixture.create_database_table(user=user)
text_field = data_fixture.create_text_field(table=table, name=uuid.uuid4())
grid = data_fixture.create_grid_view(table=table)
sort = data_fixture.create_view_sort(view=grid, field=text_field, order="ASC")
ViewIndexingHandler.update_index(view=grid)
grid.refresh_from_db()
assert grid.db_index_name is not None
# Change the field type from text to number
url = reverse("api:database:fields:item", kwargs={"field_id": text_field.id})
response = api_client.patch(
url, {"type": "number"}, format="json", HTTP_AUTHORIZATION=f"JWT {token}"
)
assert response.status_code == HTTP_200_OK
# Sort is not removed
assert ViewSort.objects.filter(id=sort.id).exists()
# Sort index is removed
grid.refresh_from_db()
assert grid.db_index_name is None
@pytest.mark.django_db
def test_delete_field(api_client, data_fixture):
user, token = data_fixture.create_user_and_token()
@ -858,8 +892,7 @@ def test_update_field_returns_with_error_if_cant_lock_field_if_locked_for_update
with conn.cursor() as cursor:
# nosec
cursor.execute(
f"SELECT * FROM database_field where id = {text_field.id} "
f"FOR UPDATE"
f"SELECT * FROM database_field where id = {text_field.id} FOR UPDATE"
)
response = api_client.patch(
url,

View file

@ -0,0 +1,8 @@
{
"type": "bug",
"message": "Allow change field type with active sort",
"domain": "database",
"issue_number": 3519,
"bullet_points": [],
"created_at": "2025-03-18"
}