Skip to content

Commit a2f9ee7

Browse files
authored
Handle duplicate error gracefully for Page/Workflow rename (baserow#4271)
* Ensure that duplicate workflow name error is correctly handled * Ensure that duplicate page name error is correctly handled * Add builder changelog * Remove dev-specific safety checks. * Add automation changelog
1 parent a2fc6da commit a2f9ee7

File tree

10 files changed

+82
-4
lines changed

10 files changed

+82
-4
lines changed

backend/src/baserow/contrib/automation/workflows/handler.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
from baserow.contrib.automation.workflows.types import UpdatedAutomationWorkflow
4747
from baserow.core.cache import global_cache, local_cache
4848
from baserow.core.exceptions import IdDoesNotExist
49+
from baserow.core.psycopg import is_unique_violation_error
4950
from baserow.core.registries import ImportExportConfig
5051
from baserow.core.services.exceptions import DispatchException
5152
from baserow.core.storage import ExportZipFile, get_default_storage
@@ -247,7 +248,7 @@ def update_workflow(
247248
try:
248249
workflow.save()
249250
except IntegrityError as e:
250-
if "unique constraint" in e.args[0] and "name" in e.args[0]:
251+
if is_unique_violation_error(e) and "name" in str(e):
251252
raise AutomationWorkflowNameNotUnique(
252253
name=workflow.name, automation_id=workflow.automation_id
253254
) from e

backend/src/baserow/contrib/builder/pages/handler.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
)
4343
from baserow.core.cache import global_cache
4444
from baserow.core.exceptions import IdDoesNotExist
45+
from baserow.core.psycopg import is_unique_violation_error
4546
from baserow.core.storage import ExportZipFile
4647
from baserow.core.user_sources.user_source_user import UserSourceUser
4748
from baserow.core.utils import ChildProgressBuilder, MirrorDict, find_unused_name
@@ -201,9 +202,9 @@ def update_page(self, page: Page, **kwargs) -> Page:
201202
try:
202203
page.save()
203204
except IntegrityError as e:
204-
if "unique constraint" in e.args[0] and "name" in e.args[0]:
205+
if is_unique_violation_error(e) and "name" in e.args[0]:
205206
raise PageNameNotUnique(name=page.name, builder_id=page.builder_id)
206-
if "unique constraint" in e.args[0] and "path" in e.args[0]:
207+
if is_unique_violation_error(e) and "path" in e.args[0]:
207208
raise PagePathNotUnique(path=page.path, builder_id=page.builder_id)
208209
raise e
209210

backend/tests/baserow/contrib/automation/api/workflows/test_workflow_views.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -596,3 +596,26 @@ def test_get_workflow_history_permission_error(api_client, data_fixture):
596596
"detail": "You don't have the required permission to execute this operation.",
597597
"error": "PERMISSION_DENIED",
598598
}
599+
600+
601+
@pytest.mark.django_db
602+
def test_rename_workflow_using_existing_workflow_name(api_client, data_fixture):
603+
user, token = data_fixture.create_user_and_token()
604+
automation = data_fixture.create_automation_application(user)
605+
workflow_1 = data_fixture.create_automation_workflow(
606+
user, automation=automation, name="test1", order=1
607+
)
608+
workflow_2 = data_fixture.create_automation_workflow(
609+
user, automation=automation, name="test2", order=2
610+
)
611+
612+
url = reverse(API_URL_WORKFLOW_ITEM, kwargs={"workflow_id": workflow_2.id})
613+
response = api_client.patch(
614+
url,
615+
{"name": workflow_1.name},
616+
format="json",
617+
HTTP_AUTHORIZATION=f"JWT {token}",
618+
)
619+
620+
assert response.status_code == HTTP_400_BAD_REQUEST
621+
assert response.json()["error"] == "ERROR_AUTOMATION_WORKFLOW_NAME_NOT_UNIQUE"

backend/tests/baserow/contrib/builder/api/pages/test_page_views.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -709,3 +709,24 @@ def test_delete_shared_page(api_client, data_fixture):
709709

710710
assert response.status_code == HTTP_400_BAD_REQUEST
711711
assert response.json()["error"] == "ERROR_SHARED_PAGE_READ_ONLY"
712+
713+
714+
@pytest.mark.django_db
715+
def test_rename_page_using_existing_page_name(api_client, data_fixture):
716+
user, token = data_fixture.create_user_and_token()
717+
builder = data_fixture.create_builder_application(user=user)
718+
page_1 = data_fixture.create_builder_page(builder=builder, order=1, name="test1")
719+
page_2 = data_fixture.create_builder_page(builder=builder, order=1, name="test2")
720+
721+
url = reverse("api:builder:pages:item", kwargs={"page_id": page_2.id})
722+
response = api_client.patch(
723+
url,
724+
{
725+
"name": page_1.name,
726+
},
727+
format="json",
728+
HTTP_AUTHORIZATION=f"JWT {token}",
729+
)
730+
731+
assert response.status_code == HTTP_400_BAD_REQUEST
732+
assert response.json()["error"] == "ERROR_PAGE_NAME_NOT_UNIQUE"
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"type": "bug",
3+
"message": "Show a specific error when a page is being renamed to an existing page's name.",
4+
"issue_origin": "github",
5+
"issue_number": 4183,
6+
"domain": "builder",
7+
"bullet_points": [],
8+
"created_at": "2025-11-17"
9+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"type": "bug",
3+
"message": "Show a specific error when a workflow is being renamed to an existing workflow's name.",
4+
"issue_origin": "github",
5+
"issue_number": 4183,
6+
"domain": "automation",
7+
"bullet_points": [],
8+
"created_at": "2025-12-08"
9+
}

web-frontend/modules/automation/locales/en.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,8 @@
4646
"duplicatedTitle": "Workflow duplicated"
4747
},
4848
"automationWorkflowErrors": {
49-
"errorNameNotUnique": "A workflow with this name already exists"
49+
"errorNameNotUnique": "A workflow with this name already exists",
50+
"errorNameNotUniqueDescription": "Please enter a unique name for the workflow"
5051
},
5152
"trashType": {
5253
"workflow": "workflow",

web-frontend/modules/automation/plugin.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,12 @@ export default (context) => {
7070

7171
registerRealtimeEvents(app.$realtime)
7272

73+
app.$clientErrorMap.setError(
74+
'ERROR_AUTOMATION_WORKFLOW_NAME_NOT_UNIQUE',
75+
app.i18n.t('automationWorkflowErrors.errorNameNotUnique'),
76+
app.i18n.t('automationWorkflowErrors.errorNameNotUniqueDescription')
77+
)
78+
7379
store.registerModule('automationApplication', automationApplicationStore)
7480
store.registerModule('automationWorkflow', automationWorkflowStore)
7581
store.registerModule('automationWorkflowNode', automationWorkflowNodeStore)

web-frontend/modules/builder/locales/en.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@
5656
},
5757
"pageErrors": {
5858
"errorNameNotUnique": "A page with this name already exists",
59+
"errorNameNotUniqueDescription": "Please enter a unique name for the page",
5960
"errorPathNotUnique": "A path with this name already exists",
6061
"errorStartingSlash": "A path needs to start with a '/'",
6162
"errorValidPathCharacters": "The path contains invalid characters",

web-frontend/modules/builder/plugin.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,12 @@ export default (context) => {
172172

173173
registerRealtimeEvents(app.$realtime)
174174

175+
app.$clientErrorMap.setError(
176+
'ERROR_PAGE_NAME_NOT_UNIQUE',
177+
app.i18n.t('pageErrors.errorNameNotUnique'),
178+
app.i18n.t('pageErrors.errorNameNotUniqueDescription')
179+
)
180+
175181
store.registerModule('page', pageStore)
176182
store.registerModule('element', elementStore)
177183
store.registerModule('domain', domainStore)

0 commit comments

Comments
 (0)