mirror of
https://gitlab.com/bramw/baserow.git
synced 2025-03-17 05:52:42 +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:
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
|
@ -2465,27 +2465,24 @@ 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["field"], name
|
||||
)
|
||||
try:
|
||||
search_values.append(
|
||||
primary_field_type.parse_field_value_for_db(
|
||||
primary_field["field"], name
|
||||
)
|
||||
except ValidationError as e:
|
||||
error = ValidationError(
|
||||
f"The value '{name}' is an invalid value for the primary field "
|
||||
"of the linked table.",
|
||||
code="invalid_value",
|
||||
)
|
||||
if continue_on_error:
|
||||
# Replace values by error for failing rows
|
||||
for row_index in row_ids:
|
||||
values_by_row[row_index] = error
|
||||
else:
|
||||
raise e
|
||||
)
|
||||
except ValidationError as e:
|
||||
error = ValidationError(
|
||||
f"The value '{name}' is an invalid value for the primary field "
|
||||
"of the linked table.",
|
||||
code="invalid_value",
|
||||
)
|
||||
if continue_on_error:
|
||||
# Replace values by error for failing rows
|
||||
for row_index in row_ids:
|
||||
values_by_row[row_index] = error
|
||||
else:
|
||||
raise e
|
||||
|
||||
# Get all matching rows
|
||||
rows = related_model.objects.filter(
|
||||
|
@ -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
|
||||
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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"
|
||||
}
|
Loading…
Reference in a new issue