Skip to content

Commit adcfdac

Browse files
authored
fix: ensure local baserow upsert service handles field constraint errors (baserow#5081)
* Ensure local baserow upsert handles unique constraint * Fix hard-coded ids * Reword 'unique' to 'field'
1 parent 8d9e3dc commit adcfdac

3 files changed

Lines changed: 130 additions & 0 deletions

File tree

backend/src/baserow/contrib/integrations/local_baserow/service_types.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
)
3131
from baserow.contrib.database.api.utils import extract_field_ids_from_list
3232
from baserow.contrib.database.fields.exceptions import (
33+
FieldDataConstraintException,
3334
FieldDoesNotExist,
3435
IncompatibleField,
3536
)
@@ -2143,6 +2144,10 @@ def dispatch_data(
21432144
raise ServiceImproperlyConfiguredDispatchException(
21442145
f"The row with id {row_id} does not exist."
21452146
) from exc
2147+
except FieldDataConstraintException as exc:
2148+
raise InvalidContextContentDispatchException(
2149+
f"The row with id {row_id} violates a field constraint."
2150+
) from exc
21462151
else:
21472152
try:
21482153
(row,) = CreateRowsActionType.do(
@@ -2156,6 +2161,11 @@ def dispatch_data(
21562161
f"Cannot create rows in table {table.id} because "
21572162
"it has a data sync."
21582163
) from exc
2164+
except FieldDataConstraintException as exc:
2165+
raise InvalidContextContentDispatchException(
2166+
f"Cannot create rows in table {table.id} because "
2167+
"it violates a field constraint."
2168+
) from exc
21592169

21602170
return {
21612171
"data": row,

backend/tests/baserow/contrib/builder/api/workflow_actions/test_workflow_actions_views.py

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,10 @@
2222
NotificationWorkflowActionType,
2323
UpdateRowWorkflowActionType,
2424
)
25+
from baserow.contrib.database.fields.field_constraints import (
26+
TextTypeUniqueWithEmptyConstraint,
27+
)
28+
from baserow.contrib.database.fields.handler import FieldHandler
2529
from baserow.contrib.database.rows.handler import RowHandler
2630
from baserow.contrib.database.table.handler import TableHandler
2731
from baserow.contrib.integrations.local_baserow.service_types import (
@@ -638,6 +642,58 @@ def test_dispatch_local_baserow_create_row_workflow_action(api_client, data_fixt
638642
assert animal_field.name not in response_json
639643

640644

645+
@pytest.mark.django_db
646+
def test_dispatch_local_baserow_create_row_workflow_action_field_constraint(
647+
api_client, data_fixture
648+
):
649+
user, token = data_fixture.create_user_and_token()
650+
table = data_fixture.create_database_table(user=user)
651+
652+
# Create a field that has a constraint, e.g. unique
653+
fruit_field = FieldHandler().create_field(
654+
user=user,
655+
table=table,
656+
type_name="text",
657+
name="Unique fruit",
658+
field_constraints=[
659+
{"type_name": TextTypeUniqueWithEmptyConstraint.constraint_name}
660+
],
661+
)
662+
663+
model = table.get_model()
664+
model.objects.create(**{f"field_{fruit_field.id}": "Apple"})
665+
666+
builder = data_fixture.create_builder_application(user=user)
667+
page = data_fixture.create_builder_page(user=user, builder=builder)
668+
element = data_fixture.create_builder_button_element(page=page)
669+
workflow_action = data_fixture.create_local_baserow_create_row_workflow_action(
670+
page=page, element=element, event=EventTypes.CLICK, user=user
671+
)
672+
service = workflow_action.service.specific
673+
service.table = table
674+
service.field_mappings.create(field=fruit_field, value="'Apple'")
675+
service.save()
676+
677+
url = reverse(
678+
"api:builder:workflow_action:dispatch",
679+
kwargs={"workflow_action_id": workflow_action.id},
680+
)
681+
response = api_client.post(
682+
url,
683+
{},
684+
format="json",
685+
HTTP_AUTHORIZATION=f"JWT {token}",
686+
)
687+
688+
assert response.status_code == HTTP_400_BAD_REQUEST
689+
response_json = response.json()
690+
assert response_json["error"] == "ERROR_SERVICE_INVALID_DISPATCH_CONTEXT_CONTENT"
691+
assert (
692+
response_json["detail"]
693+
== f"Cannot create rows in table {table.id} because it violates a field constraint."
694+
)
695+
696+
641697
@pytest.mark.django_db
642698
def test_dispatch_local_baserow_update_row_workflow_action(api_client, data_fixture):
643699
user, token = data_fixture.create_user_and_token()
@@ -696,6 +752,61 @@ def test_dispatch_local_baserow_update_row_workflow_action(api_client, data_fixt
696752
assert animal_field.name not in response_json
697753

698754

755+
@pytest.mark.django_db
756+
def test_dispatch_local_baserow_update_row_workflow_action_field_constraint(
757+
api_client, data_fixture
758+
):
759+
user, token = data_fixture.create_user_and_token()
760+
table = data_fixture.create_database_table(user=user)
761+
762+
# Create a field that has a constraint, e.g. unique
763+
fruit_field = FieldHandler().create_field(
764+
user=user,
765+
table=table,
766+
type_name="text",
767+
name="Unique fruit",
768+
field_constraints=[
769+
{"type_name": TextTypeUniqueWithEmptyConstraint.constraint_name}
770+
],
771+
)
772+
773+
model = table.get_model()
774+
model.objects.create(**{f"field_{fruit_field.id}": "Apple"})
775+
# Create another row, which we'll update to 'Apple' to simulate a unique constraint
776+
second_row = model.objects.create(**{f"field_{fruit_field.id}": "Banana"})
777+
778+
builder = data_fixture.create_builder_application(user=user)
779+
page = data_fixture.create_builder_page(user=user, builder=builder)
780+
element = data_fixture.create_builder_button_element(page=page)
781+
workflow_action = data_fixture.create_local_baserow_create_row_workflow_action(
782+
page=page, element=element, event=EventTypes.CLICK, user=user
783+
)
784+
service = workflow_action.service.specific
785+
service.table = table
786+
service.row_id = f"'{second_row.id}'"
787+
service.field_mappings.create(field=fruit_field, value="'Apple'")
788+
service.save()
789+
790+
url = reverse(
791+
"api:builder:workflow_action:dispatch",
792+
kwargs={"workflow_action_id": workflow_action.id},
793+
)
794+
response = api_client.post(
795+
url,
796+
{},
797+
format="json",
798+
HTTP_AUTHORIZATION=f"JWT {token}",
799+
)
800+
801+
assert response.status_code == HTTP_400_BAD_REQUEST
802+
response_json = response.json()
803+
assert response_json["error"] == "ERROR_SERVICE_INVALID_DISPATCH_CONTEXT_CONTENT"
804+
assert (
805+
response_json["detail"]
806+
== f"The row with id {second_row.id} violates a field constraint."
807+
)
808+
809+
699810
@pytest.mark.django_db
700811
def test_dispatch_local_baserow_upsert_row_workflow_action_with_current_record(
701812
api_client, data_fixture
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"type": "bug",
3+
"message": "Ensure Local Baserow Upsert integration handles field constraint errors.",
4+
"issue_origin": "github",
5+
"issue_number": null,
6+
"domain": "integration",
7+
"bullet_points": [],
8+
"created_at": "2026-03-30"
9+
}

0 commit comments

Comments
 (0)