From 0fdf4838fd30dc30cee00e2b4055c5071e6c7cf7 Mon Sep 17 00:00:00 2001 From: Tsering Paljor Date: Mon, 18 May 2026 10:00:31 +0700 Subject: [PATCH 1/3] Removed unique constraint on Builder page names. (#5328) --- .../baserow/contrib/builder/api/pages/errors.py | 6 ------ .../baserow/contrib/builder/api/pages/views.py | 6 ------ .../0068_alter_page_unique_together.py | 17 +++++++++++++++++ .../baserow/contrib/builder/pages/exceptions.py | 14 -------------- .../baserow/contrib/builder/pages/handler.py | 5 ----- .../src/baserow/contrib/builder/pages/models.py | 2 +- .../builder/api/pages/test_page_views.py | 17 +++++++++++------ .../contrib/builder/pages/test_page_handler.py | 11 +++++------ ...unique_constraint_on_builder_page_names.json | 9 +++++++++ .../page/settings/PageSettingsForm.vue | 7 ------- web-frontend/modules/builder/locales/en.json | 2 -- web-frontend/modules/builder/plugin.js | 8 +------- 12 files changed, 44 insertions(+), 60 deletions(-) create mode 100644 backend/src/baserow/contrib/builder/migrations/0068_alter_page_unique_together.py create mode 100644 changelog/entries/unreleased/refactor/5042_removed_unique_constraint_on_builder_page_names.json diff --git a/backend/src/baserow/contrib/builder/api/pages/errors.py b/backend/src/baserow/contrib/builder/api/pages/errors.py index 85051f79c3..18ec998fa0 100644 --- a/backend/src/baserow/contrib/builder/api/pages/errors.py +++ b/backend/src/baserow/contrib/builder/api/pages/errors.py @@ -19,12 +19,6 @@ "The page id {e.page_id} does not belong to the builder.", ) -ERROR_PAGE_NAME_NOT_UNIQUE = ( - "ERROR_PAGE_NAME_NOT_UNIQUE", - HTTP_400_BAD_REQUEST, - "The page name {e.name} already exists for your builder instance.", -) - ERROR_PAGE_PATH_NOT_UNIQUE = ( "ERROR_PAGE_PATH_NOT_UNIQUE", HTTP_400_BAD_REQUEST, diff --git a/backend/src/baserow/contrib/builder/api/pages/views.py b/backend/src/baserow/contrib/builder/api/pages/views.py index 5e7f036a69..2540cfbe7b 100644 --- a/backend/src/baserow/contrib/builder/api/pages/views.py +++ b/backend/src/baserow/contrib/builder/api/pages/views.py @@ -19,7 +19,6 @@ ERROR_DUPLICATE_QUERY_PARAMS, ERROR_INVALID_QUERY_PARAM_NAME, ERROR_PAGE_DOES_NOT_EXIST, - ERROR_PAGE_NAME_NOT_UNIQUE, ERROR_PAGE_NOT_IN_BUILDER, ERROR_PAGE_PATH_NOT_UNIQUE, ERROR_PATH_PARAM_NOT_DEFINED, @@ -38,7 +37,6 @@ DuplicatePathParamsInPath, InvalidQueryParamName, PageDoesNotExist, - PageNameNotUnique, PageNotInBuilder, PagePathNotUnique, PathParamNotDefined, @@ -76,7 +74,6 @@ class PagesView(APIView): 400: get_error_schema( [ "ERROR_REQUEST_BODY_VALIDATION", - "ERROR_PAGE_NAME_NOT_UNIQUE", "ERROR_PAGE_PATH_NOT_UNIQUE", "ERROR_PATH_PARAM_NOT_IN_PATH", "ERROR_PATH_PARAM_NOT_DEFINED", @@ -91,7 +88,6 @@ class PagesView(APIView): @map_exceptions( { ApplicationDoesNotExist: ERROR_APPLICATION_DOES_NOT_EXIST, - PageNameNotUnique: ERROR_PAGE_NAME_NOT_UNIQUE, PagePathNotUnique: ERROR_PAGE_PATH_NOT_UNIQUE, PathParamNotInPath: ERROR_PATH_PARAM_NOT_IN_PATH, PathParamNotDefined: ERROR_PATH_PARAM_NOT_DEFINED, @@ -137,7 +133,6 @@ class PageView(APIView): 400: get_error_schema( [ "ERROR_REQUEST_BODY_VALIDATION", - "ERROR_PAGE_NAME_NOT_UNIQUE", "ERROR_PAGE_PATH_NOT_UNIQUE", "ERROR_PATH_PARAM_NOT_IN_PATH", "ERROR_PATH_PARAM_NOT_DEFINED", @@ -156,7 +151,6 @@ class PageView(APIView): { ApplicationDoesNotExist: ERROR_APPLICATION_DOES_NOT_EXIST, PageDoesNotExist: ERROR_PAGE_DOES_NOT_EXIST, - PageNameNotUnique: ERROR_PAGE_NAME_NOT_UNIQUE, PagePathNotUnique: ERROR_PAGE_PATH_NOT_UNIQUE, PathParamNotInPath: ERROR_PATH_PARAM_NOT_IN_PATH, PathParamNotDefined: ERROR_PATH_PARAM_NOT_DEFINED, diff --git a/backend/src/baserow/contrib/builder/migrations/0068_alter_page_unique_together.py b/backend/src/baserow/contrib/builder/migrations/0068_alter_page_unique_together.py new file mode 100644 index 0000000000..dd7e59be31 --- /dev/null +++ b/backend/src/baserow/contrib/builder/migrations/0068_alter_page_unique_together.py @@ -0,0 +1,17 @@ +# Generated by Django 5.2.13 on 2026-05-07 08:31 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('builder', '0067_slackwritemessageworkflowaction'), + ] + + operations = [ + migrations.AlterUniqueTogether( + name='page', + unique_together={('builder', 'path')}, + ), + ] diff --git a/backend/src/baserow/contrib/builder/pages/exceptions.py b/backend/src/baserow/contrib/builder/pages/exceptions.py index 9a8bbf96db..b2d3135fef 100644 --- a/backend/src/baserow/contrib/builder/pages/exceptions.py +++ b/backend/src/baserow/contrib/builder/pages/exceptions.py @@ -23,20 +23,6 @@ class SharedPageIsReadOnly(Exception): """Raised when trying to do something on shared page.""" -class PageNameNotUnique(Exception): - """Raised when a page is trying to be created with a name that already exists""" - - def __init__(self, name=None, builder_id=None, *args, **kwargs): - self.name = name - self.builder_id = builder_id - super().__init__( - f"A page with the name {name} already exists in the builder with id " - f"{builder_id}", - *args, - **kwargs, - ) - - class PagePathNotUnique(Exception): """Raised when a page is trying to be created with a path that already exists""" diff --git a/backend/src/baserow/contrib/builder/pages/handler.py b/backend/src/baserow/contrib/builder/pages/handler.py index e183c9f356..26ea57c130 100644 --- a/backend/src/baserow/contrib/builder/pages/handler.py +++ b/backend/src/baserow/contrib/builder/pages/handler.py @@ -24,7 +24,6 @@ DuplicatePathParamsInPath, InvalidQueryParamName, PageDoesNotExist, - PageNameNotUnique, PageNotInBuilder, PagePathNotUnique, PathParamNotDefined, @@ -144,8 +143,6 @@ def create_page( shared=shared, ) except IntegrityError as e: - if "unique constraint" in e.args[0] and "name" in e.args[0]: - raise PageNameNotUnique(name=name, builder_id=builder.id) if "unique constraint" in e.args[0] and "path" in e.args[0]: raise PagePathNotUnique(path=path, builder_id=builder.id) raise e @@ -203,8 +200,6 @@ def update_page(self, page: Page, **kwargs) -> Page: try: page.save() except IntegrityError as e: - if is_unique_violation_error(e) and "name" in e.args[0]: - raise PageNameNotUnique(name=page.name, builder_id=page.builder_id) if is_unique_violation_error(e) and "path" in e.args[0]: raise PagePathNotUnique(path=page.path, builder_id=page.builder_id) raise e diff --git a/backend/src/baserow/contrib/builder/pages/models.py b/backend/src/baserow/contrib/builder/pages/models.py index 4f92fa2917..815ec94cea 100644 --- a/backend/src/baserow/contrib/builder/pages/models.py +++ b/backend/src/baserow/contrib/builder/pages/models.py @@ -91,7 +91,7 @@ class Meta: "-shared", # First page is the shared one if any. "order", ) - unique_together = [["builder", "name"], ["builder", "path"]] + unique_together = [["builder", "path"]] indexes = [ models.Index(fields=["-shared", "order"]), models.Index(fields=["builder", "-shared", "order"]), diff --git a/backend/tests/baserow/contrib/builder/api/pages/test_page_views.py b/backend/tests/baserow/contrib/builder/api/pages/test_page_views.py index b9a90e53c1..ef53b83180 100644 --- a/backend/tests/baserow/contrib/builder/api/pages/test_page_views.py +++ b/backend/tests/baserow/contrib/builder/api/pages/test_page_views.py @@ -9,6 +9,8 @@ HTTP_404_NOT_FOUND, ) +from baserow.contrib.builder.pages.models import Page + @pytest.mark.django_db def test_create_page(api_client, data_fixture): @@ -182,8 +184,9 @@ def test_create_page_duplicate_page_name(api_client, data_fixture): HTTP_AUTHORIZATION=f"JWT {token}", ) - assert response.status_code == HTTP_400_BAD_REQUEST - assert response.json()["error"] == "ERROR_PAGE_NAME_NOT_UNIQUE" + assert response.status_code == HTTP_200_OK + duplicated_page = Page.objects.get(id=response.json()["id"]) + assert duplicated_page.name == page.name @pytest.mark.django_db @@ -387,8 +390,9 @@ def test_update_page_duplicate_page_name(api_client, data_fixture): HTTP_AUTHORIZATION=f"JWT {token}", ) - assert response.status_code == HTTP_400_BAD_REQUEST - assert response.json()["error"] == "ERROR_PAGE_NAME_NOT_UNIQUE" + assert response.status_code == HTTP_200_OK + page_two.refresh_from_db() + assert page_two.name == page.name @pytest.mark.django_db @@ -728,5 +732,6 @@ def test_rename_page_using_existing_page_name(api_client, data_fixture): HTTP_AUTHORIZATION=f"JWT {token}", ) - assert response.status_code == HTTP_400_BAD_REQUEST - assert response.json()["error"] == "ERROR_PAGE_NAME_NOT_UNIQUE" + assert response.status_code == HTTP_200_OK + page_2.refresh_from_db() + assert page_2.name == "test1" diff --git a/backend/tests/baserow/contrib/builder/pages/test_page_handler.py b/backend/tests/baserow/contrib/builder/pages/test_page_handler.py index 8c1a2f6404..7093991de0 100644 --- a/backend/tests/baserow/contrib/builder/pages/test_page_handler.py +++ b/backend/tests/baserow/contrib/builder/pages/test_page_handler.py @@ -9,7 +9,6 @@ DuplicatePathParamsInPath, InvalidQueryParamName, PageDoesNotExist, - PageNameNotUnique, PageNotInBuilder, PagePathNotUnique, PathParamNotDefined, @@ -59,8 +58,8 @@ def test_create_page(data_fixture): def test_create_page_page_name_not_unique(data_fixture): page = data_fixture.create_builder_page(name="test", path="/test") - with pytest.raises(PageNameNotUnique): - PageHandler().create_page(page.builder, name="test", path="/new") + new_page = PageHandler().create_page(page.builder, name="test", path="/new") + assert new_page.name == page.name @pytest.mark.django_db @@ -152,9 +151,9 @@ def test_update_shared_page(data_fixture): def test_update_page_page_name_not_unique(data_fixture): page = data_fixture.create_builder_page(name="test") page_two = data_fixture.create_builder_page(builder=page.builder, name="test2") - - with pytest.raises(PageNameNotUnique): - PageHandler().update_page(page_two, name=page.name) + PageHandler().update_page(page_two, name=page.name) + page_two.refresh_from_db() + assert page_two.name == page.name @pytest.mark.django_db diff --git a/changelog/entries/unreleased/refactor/5042_removed_unique_constraint_on_builder_page_names.json b/changelog/entries/unreleased/refactor/5042_removed_unique_constraint_on_builder_page_names.json new file mode 100644 index 0000000000..a8fd8f9962 --- /dev/null +++ b/changelog/entries/unreleased/refactor/5042_removed_unique_constraint_on_builder_page_names.json @@ -0,0 +1,9 @@ +{ + "type": "refactor", + "message": "Removed the unique constraint on Builder page names.", + "issue_origin": "github", + "issue_number": 5042, + "domain": "builder", + "bullet_points": [], + "created_at": "2026-05-07" +} diff --git a/web-frontend/modules/builder/components/page/settings/PageSettingsForm.vue b/web-frontend/modules/builder/components/page/settings/PageSettingsForm.vue index b39ce3d349..09c853e695 100644 --- a/web-frontend/modules/builder/components/page/settings/PageSettingsForm.vue +++ b/web-frontend/modules/builder/components/page/settings/PageSettingsForm.vue @@ -282,9 +282,6 @@ export default { addQueryParam(newParam) { this.localQueryParams.push(newParam) }, - isNameUnique(name) { - return !this.pageNames.includes(name) || name === this.page?.name - }, isPathUnique(path) { const pathGeneralised = this.generalisePath(path) return ( @@ -340,10 +337,6 @@ export default { this.$t('error.requiredField'), required ), - isUnique: helpers.withMessage( - this.$t('pageErrors.errorNameNotUnique'), - this.isNameUnique - ), maxLength: helpers.withMessage( this.$t('error.maxLength', { max: 255 }), maxLength(225) diff --git a/web-frontend/modules/builder/locales/en.json b/web-frontend/modules/builder/locales/en.json index 856b397cd4..3ab0fe3e10 100644 --- a/web-frontend/modules/builder/locales/en.json +++ b/web-frontend/modules/builder/locales/en.json @@ -55,8 +55,6 @@ "submit": "Add page" }, "pageErrors": { - "errorNameNotUnique": "A page with this name already exists", - "errorNameNotUniqueDescription": "Please enter a unique name for the page", "errorPathNotUnique": "A path with this name already exists", "errorStartingSlash": "A path needs to start with a '/'", "errorValidPathCharacters": "The path contains invalid characters", diff --git a/web-frontend/modules/builder/plugin.js b/web-frontend/modules/builder/plugin.js index 01a8b90c71..dd566abed2 100644 --- a/web-frontend/modules/builder/plugin.js +++ b/web-frontend/modules/builder/plugin.js @@ -158,15 +158,9 @@ export default defineNuxtPlugin({ name: 'builder', dependsOn: ['core', 'store'], async setup(nuxtApp) { - const { $store, $registry, $clientErrorMap, $i18n } = nuxtApp + const { $store, $registry } = nuxtApp const context = { app: nuxtApp } - $clientErrorMap.setError( - 'ERROR_PAGE_NAME_NOT_UNIQUE', - $i18n.t('pageErrors.errorNameNotUnique'), - $i18n.t('pageErrors.errorNameNotUniqueDescription') - ) - $store.registerModuleNuxtSafe('page', pageStore) $store.registerModuleNuxtSafe('element', elementStore) $store.registerModuleNuxtSafe('domain', domainStore) From 74e89389948ca06ed8b05700363b55d2a30b6884 Mon Sep 17 00:00:00 2001 From: Tsering Paljor Date: Mon, 18 May 2026 10:00:39 +0700 Subject: [PATCH 2/3] Make original_workflow non-nullable again after Automation Node History was deployed. (#5326) --- .../contrib/automation/history/models.py | 3 -- ...mationworkflowhistory_original_workflow.py | 32 +++++++++++++++++++ ...historyoriginal_workflow_non_nullable.json | 9 ++++++ 3 files changed, 41 insertions(+), 3 deletions(-) create mode 100644 backend/src/baserow/contrib/automation/migrations/0029_alter_automationworkflowhistory_original_workflow.py create mode 100644 changelog/entries/unreleased/refactor/5236_make_automationworkflowhistoryoriginal_workflow_non_nullable.json diff --git a/backend/src/baserow/contrib/automation/history/models.py b/backend/src/baserow/contrib/automation/history/models.py index 67514ef6ef..fca3cd715b 100644 --- a/backend/src/baserow/contrib/automation/history/models.py +++ b/backend/src/baserow/contrib/automation/history/models.py @@ -24,9 +24,6 @@ class AutomationWorkflowHistory(AutomationHistory): "automation.AutomationWorkflow", on_delete=models.CASCADE, related_name="workflow_histories", - # TODO ZDM: Make non-nullable after next release and add backfill - # migration. See: https://github.com/baserow/baserow/issues/5236 - null=True, ) workflow = models.ForeignKey( "automation.AutomationWorkflow", diff --git a/backend/src/baserow/contrib/automation/migrations/0029_alter_automationworkflowhistory_original_workflow.py b/backend/src/baserow/contrib/automation/migrations/0029_alter_automationworkflowhistory_original_workflow.py new file mode 100644 index 0000000000..324ae90b1a --- /dev/null +++ b/backend/src/baserow/contrib/automation/migrations/0029_alter_automationworkflowhistory_original_workflow.py @@ -0,0 +1,32 @@ +# Generated by Django 5.2.13 on 2026-05-07 07:08 + +import django.db.models.deletion +from django.db import migrations, models + + +def backfill_original_workflow(apps, schema_editor): + AutomationWorkflowHistory = apps.get_model( + "automation", "AutomationWorkflowHistory" + ) + AutomationWorkflowHistory.objects.filter(original_workflow__isnull=True).update( + original_workflow_id=models.F("workflow_id") + ) + + +class Migration(migrations.Migration): + + dependencies = [ + ('automation', '0028_automationworkflowhistory_original_workflow_and_more'), + ] + + operations = [ + migrations.RunPython( + backfill_original_workflow, + reverse_code=migrations.RunPython.noop, + ), + migrations.AlterField( + model_name='automationworkflowhistory', + name='original_workflow', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='workflow_histories', to='automation.automationworkflow'), + ), + ] diff --git a/changelog/entries/unreleased/refactor/5236_make_automationworkflowhistoryoriginal_workflow_non_nullable.json b/changelog/entries/unreleased/refactor/5236_make_automationworkflowhistoryoriginal_workflow_non_nullable.json new file mode 100644 index 0000000000..d02659df01 --- /dev/null +++ b/changelog/entries/unreleased/refactor/5236_make_automationworkflowhistoryoriginal_workflow_non_nullable.json @@ -0,0 +1,9 @@ +{ + "type": "refactor", + "message": "Make AutomationWorkflowHistory.original_workflow non-nullable.", + "issue_origin": "github", + "issue_number": 5236, + "domain": "automation", + "bullet_points": [], + "created_at": "2026-05-07" +} From b4c6fed5053276b84b48705b98be160a2d276d9f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 18 May 2026 09:10:47 +0200 Subject: [PATCH 3/3] chore(deps): bump axios from 1.15.0 to 1.15.2 in /e2e-tests (#5382) Bumps [axios](https://github.com/axios/axios) from 1.15.0 to 1.15.2. - [Release notes](https://github.com/axios/axios/releases) - [Changelog](https://github.com/axios/axios/blob/v1.x/CHANGELOG.md) - [Commits](https://github.com/axios/axios/compare/v1.15.0...v1.15.2) --- updated-dependencies: - dependency-name: axios dependency-version: 1.15.2 dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- e2e-tests/package.json | 2 +- e2e-tests/yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/e2e-tests/package.json b/e2e-tests/package.json index 377ad43313..01f7fd46b8 100644 --- a/e2e-tests/package.json +++ b/e2e-tests/package.json @@ -20,7 +20,7 @@ "@faker-js/faker": "7.6.0", "@nuxt/test-utils": "^3.21.0", "@playwright/test": "^1.48.0", - "axios": "1.15.0", + "axios": "1.15.2", "dotenv": "16.0.3" } } diff --git a/e2e-tests/yarn.lock b/e2e-tests/yarn.lock index f4f4b94411..6309de4f7a 100644 --- a/e2e-tests/yarn.lock +++ b/e2e-tests/yarn.lock @@ -277,10 +277,10 @@ asynckit@^0.4.0: resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== -axios@1.15.0: - version "1.15.0" - resolved "https://registry.yarnpkg.com/axios/-/axios-1.15.0.tgz#0fcee91ef03d386514474904b27863b2c683bf4f" - integrity sha512-wWyJDlAatxk30ZJer+GeCWS209sA42X+N5jU2jy6oHTp7ufw8uzUTVFBX9+wTfAlhiJXGS0Bq7X6efruWjuK9Q== +axios@1.15.2: + version "1.15.2" + resolved "https://registry.yarnpkg.com/axios/-/axios-1.15.2.tgz#eb8fb6d30349abace6ade5b4cb4d9e8a0dc23e5b" + integrity sha512-wLrXxPtcrPTsNlJmKjkPnNPK2Ihe0hn0wGSaTEiHRPxwjvJwT3hKmXF4dpqxmPO9SoNb2FsYXj/xEo0gHN+D5A== dependencies: follow-redirects "^1.15.11" form-data "^4.0.5"