diff --git a/backend/src/baserow/contrib/builder/elements/element_types.py b/backend/src/baserow/contrib/builder/elements/element_types.py index db9fdabc36..021d00187f 100644 --- a/backend/src/baserow/contrib/builder/elements/element_types.py +++ b/backend/src/baserow/contrib/builder/elements/element_types.py @@ -831,7 +831,7 @@ def deserialize_property( **kwargs, ) -> Any: if prop_name == "navigate_to_page_id" and value: - return id_mapping["builder_pages"][value] + return id_mapping["builder_pages"].get(value) return value diff --git a/backend/src/baserow/contrib/builder/workflow_actions/registries.py b/backend/src/baserow/contrib/builder/workflow_actions/registries.py index 2816d4881c..633809bbb7 100644 --- a/backend/src/baserow/contrib/builder/workflow_actions/registries.py +++ b/backend/src/baserow/contrib/builder/workflow_actions/registries.py @@ -99,7 +99,7 @@ def deserialize_property( # Migrate page id if prop_name == "page_id": - return id_mapping["builder_pages"][value] + return id_mapping["builder_pages"].get(value) # Migrate element id if prop_name == "element_id": diff --git a/backend/tests/baserow/contrib/builder/elements/test_link_collection_field_type.py b/backend/tests/baserow/contrib/builder/elements/test_link_collection_field_type.py index 43108558d2..ea70b2821b 100644 --- a/backend/tests/baserow/contrib/builder/elements/test_link_collection_field_type.py +++ b/backend/tests/baserow/contrib/builder/elements/test_link_collection_field_type.py @@ -141,3 +141,63 @@ def test_import_export_link_collection_field_type(data_fixture): "target": "self", "variant": LinkElement.VARIANTS.LINK, } + + +@pytest.mark.django_db +def test_import_link_collection_field_with_stale_page_id(data_fixture): + """ + Ensure that importing a link collection field doesn't crash when + the navigate_to_page_id is provided but doesn't exist. + """ + + user, _ = data_fixture.create_user_and_token() + page = data_fixture.create_builder_page(user=user) + table, fields, _ = data_fixture.build_table( + user=user, + columns=[ + ("Name", "text"), + ], + rows=[ + ["Foo"], + ], + ) + data_source = data_fixture.create_builder_local_baserow_list_rows_data_source( + table=table, page=page + ) + table_element = data_fixture.create_builder_table_element( + page=page, + data_source=data_source, + fields=[ + { + "name": "Foo Link Field", + "type": "link", + "config": { + "link_name": "'Buy some gold!'", + "navigate_to_url": "'https://www.kitco.com'", + "navigation_type": "custom", + "navigate_to_page_id": 100, + "page_parameters": [], + "query_parameters": [], + "target": "self", + "variant": LinkElement.VARIANTS.LINK, + }, + }, + ], + ) + + duplicated_page = PageService().duplicate_page(user, page) + data_source2 = duplicated_page.datasource_set.first() + id_mapping = { + "builder_data_sources": {data_source.id: data_source2.id}, + # the invalid page 100 isn't included + "builder_pages": {}, + } + + exported = table_element.get_type().export_serialized(table_element) + # This shouldn't fail if page 100 isn't in the mapping + imported_table_element = table_element.get_type().import_serialized( + page, exported, id_mapping + ) + imported_field = imported_table_element.fields.get(name="Foo Link Field") + assert imported_field.config["navigate_to_page_id"] is None + assert imported_field.config["navigation_type"] == "custom" diff --git a/backend/tests/baserow/contrib/builder/workflow_actions/test_workflow_action_types.py b/backend/tests/baserow/contrib/builder/workflow_actions/test_workflow_action_types.py index e747ec0119..8c4d06aca9 100644 --- a/backend/tests/baserow/contrib/builder/workflow_actions/test_workflow_action_types.py +++ b/backend/tests/baserow/contrib/builder/workflow_actions/test_workflow_action_types.py @@ -418,3 +418,20 @@ def test_builder_workflow_action_type_dispatch(workflow_action): instance.dispatch(mock_workflow_action, mock_dispatch_context) assert str(e.value) == "This service cannot be dispatched." + + +def test_workflow_action_type_deserialize_property_with_stale_page_id(): + """ + Ensure that deserializing a workflow action with an invalid page ID + doesn't cause a crash. + """ + + action_type = OpenPageWorkflowActionType() + id_mapping = {"builder_pages": {}} + + # This shouldn't fail when the page ID doesn't exist + result = action_type.deserialize_property("page_id", 100, id_mapping) + assert result is None + + result = action_type.deserialize_property("navigate_to_page_id", 100, id_mapping) + assert result is None diff --git a/changelog/entries/unreleased/bug/fixed_a_bug_that_could_cause_a_stale_page_id_to_be_set_even_.json b/changelog/entries/unreleased/bug/fixed_a_bug_that_could_cause_a_stale_page_id_to_be_set_even_.json new file mode 100644 index 0000000000..bb88c08872 --- /dev/null +++ b/changelog/entries/unreleased/bug/fixed_a_bug_that_could_cause_a_stale_page_id_to_be_set_even_.json @@ -0,0 +1,9 @@ +{ + "type": "bug", + "message": "Fixed a bug that could cause a stale page ID to be set even after deleting the page.", + "issue_origin": "github", + "issue_number": null, + "domain": "builder", + "bullet_points": [], + "created_at": "2026-03-06" +} \ No newline at end of file diff --git a/web-frontend/modules/builder/components/elements/components/forms/general/LinkNavigationSelectionForm.vue b/web-frontend/modules/builder/components/elements/components/forms/general/LinkNavigationSelectionForm.vue index fb029546ef..690d86695f 100644 --- a/web-frontend/modules/builder/components/elements/components/forms/general/LinkNavigationSelectionForm.vue +++ b/web-frontend/modules/builder/components/elements/components/forms/general/LinkNavigationSelectionForm.vue @@ -189,6 +189,7 @@ export default { this.values.navigate_to_url = '' } else if (value === 'custom') { this.values.navigation_type = 'custom' + this.values.navigate_to_page_id = null } else if (!isNaN(value)) { this.values.navigation_type = 'page' this.values.navigate_to_page_id = value