1
0
Fork 0
mirror of https://gitlab.com/bramw/baserow.git synced 2025-03-17 14:02:43 +00:00

Resolve "Rows endpoint fails hard when creating a row with a link_row field, pointing to a table with an autonumber primary field."

This commit is contained in:
Davide Silvestri 2025-01-16 09:47:16 +00:00
parent 2eef18b347
commit 542b1f2a12
5 changed files with 121 additions and 23 deletions
backend
src/baserow/contrib/database/fields
tests/baserow/contrib/database
changelog/entries/unreleased/bug

View file

@ -2465,12 +2465,9 @@ class LinkRowFieldType(
search_values = []
for name, row_ids in name_map.items():
if primary_field["type"].read_only or primary_field["field"].read_only:
search_values.append(name)
else:
try:
search_values.append(
primary_field_type.prepare_value_for_db(
primary_field_type.parse_field_value_for_db(
primary_field["field"], name
)
)
@ -6353,7 +6350,6 @@ class UUIDFieldType(ReadOnlyFieldType):
type = "uuid"
model_class = UUIDField
can_get_unique_values = False
can_be_in_form_view = False
keep_data_on_duplication = True

View file

@ -227,6 +227,21 @@ class FieldType(
return value
def parse_field_value_for_db(self, instance: Field, value: Any) -> Any:
"""
This method parses a value for a given field type and a field instance. It
fallback to the `prepare_value_for_db` method if not implemented, but it can be
customized for read_only fields where it's not possible to prepare the value for
the database, but they can be used as primary field in a table and so rows might
be queried by value.
:param instance: The field instance.
:param value: The value that needs to be validated.
:return: The modified value that could be directly saved in the database.
"""
return self.prepare_value_for_db(instance, value)
def get_search_expression(self, field: Field, queryset: QuerySet) -> Expression:
"""
When a field/row is created, updated or restored, this `FieldType` method
@ -1901,6 +1916,18 @@ class ReadOnlyFieldType(FieldType):
f"Field of type {self.type} is read only and should not be set manually."
)
def parse_field_value_for_db(self, instance: Field, value: Any) -> Any:
"""
Consider the value as valid if the field serializer can properly serialize it.
"""
try:
return self.get_serializer_field(instance).to_internal_value(value)
except serializers.ValidationError:
raise ValidationError(
f"Field of type {self.type} is read only and should not be set manually."
)
def get_export_serialized_value(
self,
row: "GeneratedTableModel",

View file

@ -36,6 +36,10 @@ from baserow.test_utils.helpers import (
assert_undo_redo_actions_are_valid,
setup_interesting_test_table,
)
from tests.baserow.contrib.database.utils import (
autonumber_field_factory,
uuid_field_factory,
)
@pytest.mark.django_db
@ -4326,3 +4330,59 @@ def test_list_rows_can_combine_view_id_with_include_exclude(
{"id": AnyInt(), "order": AnyStr(), "Name": "Paul"},
{"id": AnyInt(), "order": AnyStr(), "Name": "Jack"},
]
def number_formula_field_factory(data_fixture, table, user, **kwargs):
return data_fixture.create_formula_field(
table=table,
name="target",
number_decimal_places=1,
formula="row_id()",
**kwargs,
)
@pytest.mark.django_db
@pytest.mark.parametrize(
"primary_field_factory",
[
uuid_field_factory,
autonumber_field_factory,
number_formula_field_factory,
],
)
def test_link_row_field_validate_input_data_for_read_only_primary_fields(
data_fixture, api_client, primary_field_factory
):
# providing an incompatible value to a read-only field should be handled correctly.
# This fixes the issue #3347
user, jwt_token = data_fixture.create_user_and_token()
table_b = data_fixture.create_database_table(user=user)
pk_field = primary_field_factory(data_fixture, table_b, user, primary=True)
table_a, table_b, link_a_to_b = data_fixture.create_two_linked_tables(
user=user, table_b=table_b
)
(row_b1,) = RowHandler().create_rows(user, table_b, [{}])
row_b1_pk = str(getattr(row_b1, pk_field.db_column))
# using a valid value as reference to the row should work
response = api_client.post(
reverse("api:database:rows:batch", kwargs={"table_id": table_a.id}),
data={"items": [{link_a_to_b.db_column: [row_b1_pk]}]},
format="json",
HTTP_AUTHORIZATION=f"JWT {jwt_token}",
)
assert response.status_code == HTTP_200_OK
response_items = response.json()["items"][0][link_a_to_b.db_column]
assert response_items[0]["id"] == row_b1.id
# using an invalid value as reference to the row should fail with a 400
response = api_client.post(
reverse("api:database:rows:batch", kwargs={"table_id": table_a.id}),
data={"items": [{link_a_to_b.db_column: ["X"]}]},
format="json",
HTTP_AUTHORIZATION=f"JWT {jwt_token}",
)
assert response.status_code == HTTP_400_BAD_REQUEST

View file

@ -105,8 +105,16 @@ def phone_number_field_factory(data_fixture, table, user):
return data_fixture.create_phone_number_field(name="target", user=user, table=table)
def uuid_field_factory(data_fixture, table, user):
return data_fixture.create_uuid_field(name="target", user=user, table=table)
def uuid_field_factory(data_fixture, table, user, **kwargs):
return data_fixture.create_uuid_field(
name="target", user=user, table=table, **kwargs
)
def autonumber_field_factory(data_fixture, table, user, **kwargs):
return data_fixture.create_autonumber_field(
name="target", user=user, table=table, **kwargs
)
def single_select_field_factory(data_fixture, table, user):

View file

@ -0,0 +1,7 @@
{
"type": "bug",
"message": "Fix crash when invalid value is passed to link row fields with read-only primary field in the linked table.",
"issue_number": 3277,
"bullet_points": [],
"created_at": "2025-01-14"
}