diff --git a/backend/src/baserow/contrib/database/airtable/airtable_view_types.py b/backend/src/baserow/contrib/database/airtable/airtable_view_types.py index f3d04cb53..2be72aa4c 100644 --- a/backend/src/baserow/contrib/database/airtable/airtable_view_types.py +++ b/backend/src/baserow/contrib/database/airtable/airtable_view_types.py @@ -1,6 +1,22 @@ +from typing import Optional + +from baserow.contrib.database.airtable.constants import ( + AIRTABLE_GALLERY_VIEW_COVER_CROP_TYPE, +) +from baserow.contrib.database.airtable.import_report import ( + ERROR_TYPE_UNSUPPORTED_FEATURE, + SCOPE_VIEW, + AirtableImportReport, +) from baserow.contrib.database.airtable.registry import AirtableViewType -from baserow.contrib.database.views.models import GridView, GridViewFieldOptions -from baserow.contrib.database.views.view_types import GridViewType +from baserow.contrib.database.fields.field_types import FileFieldType +from baserow.contrib.database.views.models import ( + GalleryView, + GalleryViewFieldOptions, + GridView, + GridViewFieldOptions, +) +from baserow.contrib.database.views.view_types import GalleryViewType, GridViewType from baserow.core.utils import get_value_at_path @@ -54,3 +70,113 @@ class GridAirtableViewType(AirtableViewType): view.get_field_options = lambda *args, **kwargs: field_options return view + + +class GalleryAirtableViewType(AirtableViewType): + type = "gallery" + baserow_view_type = GalleryViewType.type + + def get_cover_column( + self, + field_mapping: dict, + view: GalleryView, + raw_airtable_table: dict, + raw_airtable_view_data: dict, + import_report: AirtableImportReport, + ) -> Optional[str]: + """ + Checks if the chosen coverColumnId is set, if the field exist, and if the + field is compatible with the cover type Baserow. If all is True, it returns + the referenced field id. + """ + + cover_column_id = get_value_at_path( + raw_airtable_view_data, "metadata.gallery.coverColumnId", None + ) + + if not cover_column_id: + return + + cover_column = field_mapping.get(cover_column_id, None) + if cover_column is None: + import_report.add_failed( + view.name, + SCOPE_VIEW, + raw_airtable_table["name"], + ERROR_TYPE_UNSUPPORTED_FEATURE, + f'View "{view.name}" cover column with id ' + f'"{cover_column_id}" is not found.', + ) + return + + if cover_column["baserow_field_type"].type != FileFieldType.type: + import_report.add_failed( + view.name, + SCOPE_VIEW, + raw_airtable_table["name"], + ERROR_TYPE_UNSUPPORTED_FEATURE, + f'View "{view.name}" cover column with id ' + f'"{cover_column_id}" is not a file field.', + ) + return + + return cover_column_id + + def prepare_view_object( + self, + field_mapping, + view: GalleryView, + raw_airtable_table, + raw_airtable_view, + raw_airtable_view_data, + config, + import_report, + ): + cover_column_id = self.get_cover_column( + field_mapping, + view, + raw_airtable_table, + raw_airtable_view_data, + import_report, + ) + view.card_cover_image_field_id = cover_column_id + + cover_fit_type = get_value_at_path( + raw_airtable_view_data, "metadata.gallery.coverFitType", None + ) + + if ( + cover_column_id + and cover_fit_type + and cover_fit_type != AIRTABLE_GALLERY_VIEW_COVER_CROP_TYPE + ): + import_report.add_failed( + view.name, + SCOPE_VIEW, + raw_airtable_table["name"], + ERROR_TYPE_UNSUPPORTED_FEATURE, + f'View "{view.name}" cover fit type "{cover_fit_type}" is not ' + f'supported, so it defaulted to "crop"', + ) + + # Map the columnOrder entries to the matching `GalleryViewFieldOptions`, + # and set that as `get_field_options`, so that it's correctly serialized + # exported. + field_options = [] + column_orders = raw_airtable_view_data.get("columnOrder", None) or [] + for index, column_order in enumerate(column_orders): + if column_order["columnId"] not in field_mapping: + continue + + field_options.append( + GalleryViewFieldOptions( + id=f"{raw_airtable_view['id']}_columnOrder_{index}", + gallery_view_id=view.id, + field_id=column_order["columnId"], + hidden=not column_order.get("visibility", True), + order=index + 1, + ) + ) + view.get_field_options = lambda *args, **kwargs: field_options + + return view diff --git a/backend/src/baserow/contrib/database/airtable/constants.py b/backend/src/baserow/contrib/database/airtable/constants.py index 39685d415..f03e61e3a 100644 --- a/backend/src/baserow/contrib/database/airtable/constants.py +++ b/backend/src/baserow/contrib/database/airtable/constants.py @@ -104,3 +104,4 @@ AIRTABLE_DATE_FILTER_VALUE_MAP = { "tomorrow": "{timeZone}??tomorrow", "yesterday": "{timeZone}??yesterday", } +AIRTABLE_GALLERY_VIEW_COVER_CROP_TYPE = "crop" diff --git a/backend/src/baserow/contrib/database/airtable/registry.py b/backend/src/baserow/contrib/database/airtable/registry.py index f4b11c538..a801072a5 100644 --- a/backend/src/baserow/contrib/database/airtable/registry.py +++ b/backend/src/baserow/contrib/database/airtable/registry.py @@ -19,6 +19,7 @@ from baserow.contrib.database.airtable.import_report import ( ERROR_TYPE_DATA_TYPE_MISMATCH, ERROR_TYPE_UNSUPPORTED_FEATURE, SCOPE_FIELD, + SCOPE_VIEW, SCOPE_VIEW_COLOR, SCOPE_VIEW_FILTER, SCOPE_VIEW_GROUP_BY, @@ -681,6 +682,36 @@ class AirtableViewType(Instance): else: return [] + def _check_personal_or_locked( + self, + view_name: str, + raw_airtable_view: dict, + raw_airtable_table: dict, + import_report: AirtableImportReport, + ) -> str: + if raw_airtable_view.get("personalForUserId", ""): + import_report.add_failed( + view_name, + SCOPE_VIEW, + raw_airtable_table["name"], + ERROR_TYPE_UNSUPPORTED_FEATURE, + f'View "{view_name}" is personal, but was made collaborative because ' + f"it can't be linked to a user. (Personal) was added to the name.", + ) + view_name += " (Personal)" + + if raw_airtable_view.get("lock", None): + import_report.add_failed( + view_name, + SCOPE_VIEW, + raw_airtable_table["name"], + ERROR_TYPE_UNSUPPORTED_FEATURE, + f'View "{view_name}" is locked, but was made collaborative because ' + f"it Baserow does not support this yet.", + ) + + return view_name + def to_serialized_baserow_view( self, field_mapping, @@ -696,11 +727,16 @@ class AirtableViewType(Instance): "The `baserow_view_type` must be implemented for the AirtableViewType." ) + view_name = raw_airtable_view["name"] + view_name = self._check_personal_or_locked( + view_name, raw_airtable_view, raw_airtable_table, import_report + ) + view_type = view_type_registry.get(self.baserow_view_type) view = view_type.model_class( id=raw_airtable_view["id"], pk=raw_airtable_view["id"], - name=raw_airtable_view["name"], + name=view_name, order=raw_airtable_table["viewOrder"].index(raw_airtable_view["id"]) + 1, ) diff --git a/backend/src/baserow/contrib/database/apps.py b/backend/src/baserow/contrib/database/apps.py index d9f9926f0..150a7a422 100755 --- a/backend/src/baserow/contrib/database/apps.py +++ b/backend/src/baserow/contrib/database/apps.py @@ -652,9 +652,13 @@ class DatabaseConfig(AppConfig): airtable_column_type_registry.register(CountAirtableColumnType()) airtable_column_type_registry.register(AutoNumberAirtableColumnType()) - from .airtable.airtable_view_types import GridAirtableViewType + from .airtable.airtable_view_types import ( + GalleryAirtableViewType, + GridAirtableViewType, + ) airtable_view_type_registry.register(GridAirtableViewType()) + airtable_view_type_registry.register(GalleryAirtableViewType()) from .airtable.airtable_filter_operators import ( AirtableContainsOperator, diff --git a/backend/src/baserow/contrib/database/views/view_types.py b/backend/src/baserow/contrib/database/views/view_types.py index a8b01374e..9a7473e3e 100644 --- a/backend/src/baserow/contrib/database/views/view_types.py +++ b/backend/src/baserow/contrib/database/views/view_types.py @@ -427,8 +427,8 @@ class GalleryViewType(ViewType): serialized = super().export_serialized(gallery, cache, files_zip, storage) - if gallery.card_cover_image_field: - serialized["card_cover_image_field_id"] = gallery.card_cover_image_field.id + if gallery.card_cover_image_field_id: + serialized["card_cover_image_field_id"] = gallery.card_cover_image_field_id serialized_field_options = [] for field_option in gallery.get_field_options(): diff --git a/backend/tests/baserow/contrib/database/airtable/test_airtable_view_types.py b/backend/tests/baserow/contrib/database/airtable/test_airtable_view_types.py index 00b4d5ceb..92f7d9187 100644 --- a/backend/tests/baserow/contrib/database/airtable/test_airtable_view_types.py +++ b/backend/tests/baserow/contrib/database/airtable/test_airtable_view_types.py @@ -6,14 +6,15 @@ import pytest from baserow.contrib.database.airtable.config import AirtableImportConfig from baserow.contrib.database.airtable.import_report import ( + SCOPE_VIEW, SCOPE_VIEW_COLOR, SCOPE_VIEW_GROUP_BY, SCOPE_VIEW_SORT, AirtableImportReport, ) from baserow.contrib.database.airtable.registry import airtable_view_type_registry -from baserow.contrib.database.fields.field_types import TextFieldType -from baserow.contrib.database.fields.models import TextField +from baserow.contrib.database.fields.field_types import FileFieldType, TextFieldType +from baserow.contrib.database.fields.models import FileField, TextField RAW_AIRTABLE_VIEW = { "id": "viwcpYeEpAs6kZspktV", @@ -62,7 +63,7 @@ FIELD_MAPPING = { "airtable_column_type": None, }, } -RAW_AIRTABLE_VIEW_DATA = { +RAW_AIRTABLE_GRID_VIEW_DATA = { "id": "viwcpYeEpAs6kZspktV", "frozenColumnCount": 1, "columnOrder": [ @@ -85,6 +86,29 @@ RAW_AIRTABLE_VIEW_DATA = { {"rowId": "rec2O9BdjKJO6dgj6QF", "visibility": True}, ], } +RAW_AIRTABLE_GALLERY_VIEW_DATA = { + "id": "viwcpYeEpAs6kZspktV", + "frozenColumnCount": 1, + "columnOrder": [ + {"columnId": "fldwSc9PqedIhTSqhi1", "visibility": True}, + {"columnId": "fldwSc9PqedIhTSqhi2", "visibility": False}, + ], + "filters": None, + "lastSortsApplied": None, + "groupLevels": None, + "colorConfig": None, + "sharesById": {}, + "metadata": {"gallery": {"coverColumnId": None, "coverFitType": "fit"}}, + "description": None, + "createdByUserId": "usrdGm7k7NIVWhK7W7L", + "applicationTransactionNumber": 284, + "rowOrder": [ + {"rowId": "recAAA5JwFXBk4swkfB", "visibility": True}, + {"rowId": "rec9Imz1INvNXgRIXn1", "visibility": True}, + {"rowId": "recyANUudYjDqIXdq9Z", "visibility": True}, + {"rowId": "rec2O9BdjKJO6dgj6QF", "visibility": True}, + ], +} RAW_VIEW_DATA_FILTERS = { "filterSet": [ { @@ -199,7 +223,7 @@ def test_import_grid_view(): ROW_ID_MAPPING, RAW_AIRTABLE_TABLE, RAW_AIRTABLE_VIEW, - RAW_AIRTABLE_VIEW_DATA, + RAW_AIRTABLE_GRID_VIEW_DATA, AirtableImportConfig(), AirtableImportReport(), ) @@ -244,8 +268,67 @@ def test_import_grid_view(): } +def test_import_personal_view(): + raw_airtable_table = deepcopy(RAW_AIRTABLE_TABLE) + raw_airtable_table["views"][0]["personalForUserId"] = "usr1234" + raw_airtable_view = raw_airtable_table["views"][0] + + airtable_view_type = airtable_view_type_registry.get("grid") + import_report = AirtableImportReport() + serialized_view = airtable_view_type.to_serialized_baserow_view( + FIELD_MAPPING, + ROW_ID_MAPPING, + raw_airtable_table, + raw_airtable_view, + RAW_AIRTABLE_GRID_VIEW_DATA, + AirtableImportConfig(), + import_report, + ) + + assert len(import_report.items) == 1 + assert import_report.items[0].object_name == "Grid view" + assert import_report.items[0].scope == SCOPE_VIEW + assert import_report.items[0].table == "Data" + assert serialized_view["name"] == "Grid view (Personal)" + + +def test_import_locked_view(): + raw_airtable_table = deepcopy(RAW_AIRTABLE_TABLE) + raw_airtable_table["views"][0]["lock"] = { + "lockLevelToEditViewName": "locked", + "lockLevelToEditViewDescription": "locked", + "lockLevelToEditViewLayout": "locked", + "lockLevelToEditViewConfig": "locked", + "lockLevelToCreateShareLink": "locked", + "lockLevelToDestroyView": "locked", + "allowFormSubmitterToIgnoreLockLevel": False, + "shouldWorkflowsRespectLockLevel": False, + "userId": "usripyu12348WK3n", + "description": None, + } + raw_airtable_view = raw_airtable_table["views"][0] + + import_report = AirtableImportReport() + airtable_view_type = airtable_view_type_registry.get("grid") + serialized_view = airtable_view_type.to_serialized_baserow_view( + FIELD_MAPPING, + ROW_ID_MAPPING, + raw_airtable_table, + raw_airtable_view, + RAW_AIRTABLE_GRID_VIEW_DATA, + AirtableImportConfig(), + import_report, + ) + + assert len(import_report.items) == 1 + assert import_report.items[0].object_name == "Grid view" + assert import_report.items[0].scope == SCOPE_VIEW + assert import_report.items[0].table == "Data" + assert serialized_view["name"] == "Grid view" + + def test_import_grid_view_xlarge_row_height(): - view_data = deepcopy(RAW_AIRTABLE_VIEW_DATA) + view_data = deepcopy(RAW_AIRTABLE_GRID_VIEW_DATA) view_data["metadata"]["grid"]["rowHeight"] = "xlarge" airtable_view_type = airtable_view_type_registry.get("grid") @@ -263,7 +346,7 @@ def test_import_grid_view_xlarge_row_height(): def test_import_grid_view_unknown_row_height(): - view_data = deepcopy(RAW_AIRTABLE_VIEW_DATA) + view_data = deepcopy(RAW_AIRTABLE_GRID_VIEW_DATA) view_data["metadata"]["grid"]["rowHeight"] = "unknown" airtable_view_type = airtable_view_type_registry.get("grid") @@ -281,7 +364,7 @@ def test_import_grid_view_unknown_row_height(): def test_import_grid_view_sorts(): - view_data = deepcopy(RAW_AIRTABLE_VIEW_DATA) + view_data = deepcopy(RAW_AIRTABLE_GRID_VIEW_DATA) view_data["lastSortsApplied"] = RAW_VIEW_DATA_SORTS airtable_view_type = airtable_view_type_registry.get("grid") serialized_view = airtable_view_type.to_serialized_baserow_view( @@ -314,7 +397,7 @@ def test_import_grid_view_sorts(): def test_import_grid_view_sort_field_not_found(): - view_data = deepcopy(RAW_AIRTABLE_VIEW_DATA) + view_data = deepcopy(RAW_AIRTABLE_GRID_VIEW_DATA) view_data["lastSortsApplied"] = RAW_VIEW_DATA_SORTS airtable_view_type = airtable_view_type_registry.get("grid") import_report = AirtableImportReport() @@ -338,7 +421,7 @@ def test_import_grid_view_sort_field_not_found(): def test_import_grid_view_sort_field_unsupported(): - view_data = deepcopy(RAW_AIRTABLE_VIEW_DATA) + view_data = deepcopy(RAW_AIRTABLE_GRID_VIEW_DATA) field_mapping = deepcopy(FIELD_MAPPING) field_mapping["fldwSc9PqedIhTSqhi1"]["baserow_field_type"]._can_order_by_types = [] @@ -365,7 +448,7 @@ def test_import_grid_view_sort_field_unsupported(): def test_import_grid_view_group_bys(): - view_data = deepcopy(RAW_AIRTABLE_VIEW_DATA) + view_data = deepcopy(RAW_AIRTABLE_GRID_VIEW_DATA) view_data["groupLevels"] = RAW_VIEW_DATA_GROUPS airtable_view_type = airtable_view_type_registry.get("grid") serialized_view = airtable_view_type.to_serialized_baserow_view( @@ -398,7 +481,7 @@ def test_import_grid_view_group_bys(): def test_import_grid_view_group_by_field_not_found(): - view_data = deepcopy(RAW_AIRTABLE_VIEW_DATA) + view_data = deepcopy(RAW_AIRTABLE_GRID_VIEW_DATA) view_data["groupLevels"] = RAW_VIEW_DATA_GROUPS airtable_view_type = airtable_view_type_registry.get("grid") import_report = AirtableImportReport() @@ -422,7 +505,7 @@ def test_import_grid_view_group_by_field_not_found(): def test_import_grid_view_group_by_field_unsupported(): - view_data = deepcopy(RAW_AIRTABLE_VIEW_DATA) + view_data = deepcopy(RAW_AIRTABLE_GRID_VIEW_DATA) field_mapping = deepcopy(FIELD_MAPPING) field_mapping["fldwSc9PqedIhTSqhi1"]["baserow_field_type"]._can_group_by = False @@ -449,7 +532,7 @@ def test_import_grid_view_group_by_field_unsupported(): def test_import_grid_view_group_by_order_unsupported(): - view_data = deepcopy(RAW_AIRTABLE_VIEW_DATA) + view_data = deepcopy(RAW_AIRTABLE_GRID_VIEW_DATA) field_mapping = deepcopy(FIELD_MAPPING) view_data["groupLevels"] = RAW_VIEW_DATA_GROUPS airtable_view_type = airtable_view_type_registry.get("grid") @@ -476,7 +559,7 @@ def test_import_grid_view_group_by_order_unsupported(): def test_import_grid_view_field_order_and_visibility(): - view_data = deepcopy(RAW_AIRTABLE_VIEW_DATA) + view_data = deepcopy(RAW_AIRTABLE_GRID_VIEW_DATA) field_mapping = deepcopy(FIELD_MAPPING) airtable_view_type = airtable_view_type_registry.get("grid") @@ -515,7 +598,7 @@ def test_import_grid_view_field_order_and_visibility(): @pytest.mark.django_db def test_import_grid_view_filters_and_groups(): - view_data = deepcopy(RAW_AIRTABLE_VIEW_DATA) + view_data = deepcopy(RAW_AIRTABLE_GRID_VIEW_DATA) field_mapping = deepcopy(FIELD_MAPPING) for field_object in field_mapping.values(): field_object["baserow_field"].content_type = ContentType.objects.get_for_model( @@ -569,7 +652,7 @@ def test_import_grid_view_filters_and_groups(): @pytest.mark.django_db def test_import_grid_view_empty_filters(): - view_data = deepcopy(RAW_AIRTABLE_VIEW_DATA) + view_data = deepcopy(RAW_AIRTABLE_GRID_VIEW_DATA) field_mapping = deepcopy(FIELD_MAPPING) for field_object in field_mapping.values(): field_object["baserow_field"].content_type = ContentType.objects.get_for_model( @@ -598,7 +681,7 @@ def test_import_grid_view_empty_filters(): @pytest.mark.django_db def test_import_grid_view_color_config_select_column_not_existing_column(): - view_data = deepcopy(RAW_AIRTABLE_VIEW_DATA) + view_data = deepcopy(RAW_AIRTABLE_GRID_VIEW_DATA) field_mapping = deepcopy(FIELD_MAPPING) for field_object in field_mapping.values(): field_object["baserow_field"].content_type = ContentType.objects.get_for_model( @@ -627,7 +710,7 @@ def test_import_grid_view_color_config_select_column_not_existing_column(): @pytest.mark.django_db def test_import_grid_view_color_config_select_column(): - view_data = deepcopy(RAW_AIRTABLE_VIEW_DATA) + view_data = deepcopy(RAW_AIRTABLE_GRID_VIEW_DATA) field_mapping = deepcopy(FIELD_MAPPING) for field_object in field_mapping.values(): field_object["baserow_field"].content_type = ContentType.objects.get_for_model( @@ -662,7 +745,7 @@ def test_import_grid_view_color_config_select_column(): @pytest.mark.django_db def test_import_grid_view_color_config_color_definitions(): - view_data = deepcopy(RAW_AIRTABLE_VIEW_DATA) + view_data = deepcopy(RAW_AIRTABLE_GRID_VIEW_DATA) field_mapping = deepcopy(FIELD_MAPPING) for field_object in field_mapping.values(): field_object["baserow_field"].content_type = ContentType.objects.get_for_model( @@ -748,3 +831,200 @@ def test_import_grid_view_color_config_color_definitions(): "order": 1, } ] + + +def test_import_gallery_view(): + view_data = deepcopy(RAW_AIRTABLE_GALLERY_VIEW_DATA) + field_mapping = deepcopy(FIELD_MAPPING) + airtable_view_type = airtable_view_type_registry.get("gallery") + + raw_airtable_view = deepcopy(RAW_AIRTABLE_VIEW) + raw_airtable_view["name"] = "Gallery view" + raw_airtable_view["type"] = "gallery" + + import_report = AirtableImportReport() + serialized_view = airtable_view_type.to_serialized_baserow_view( + field_mapping, + ROW_ID_MAPPING, + RAW_AIRTABLE_TABLE, + raw_airtable_view, + view_data, + AirtableImportConfig(), + import_report, + ) + + assert len(import_report.items) == 0 + assert serialized_view == { + "id": "viwcpYeEpAs6kZspktV", + "type": "gallery", + "name": "Gallery view", + "order": 1, + "ownership_type": "collaborative", + "owned_by": None, + "filter_type": "AND", + "filters_disabled": False, + "filters": [], + "filter_groups": [], + "sortings": [], + "decorations": [], + "public": False, + "field_options": [ + { + "id": "viwcpYeEpAs6kZspktV_columnOrder_0", + "field_id": "fldwSc9PqedIhTSqhi1", + "hidden": False, + "order": 1, + }, + { + "id": "viwcpYeEpAs6kZspktV_columnOrder_1", + "field_id": "fldwSc9PqedIhTSqhi2", + "hidden": True, + "order": 2, + }, + ], + } + + +def test_import_gallery_view_with_cover_column(): + airtable_view_type = airtable_view_type_registry.get("gallery") + + view_data = deepcopy(RAW_AIRTABLE_GALLERY_VIEW_DATA) + view_data["metadata"]["gallery"]["coverColumnId"] = "fldwSc9PqedIhTSqhi3" + view_data["metadata"]["gallery"]["coverFitType"] = "crop" + + raw_airtable_view = deepcopy(RAW_AIRTABLE_VIEW) + raw_airtable_view["name"] = "Gallery view" + raw_airtable_view["type"] = "gallery" + + raw_airtable_table = deepcopy(RAW_AIRTABLE_TABLE) + raw_airtable_table["columns"].append( + {"id": "fldwSc9PqedIhTSqhi3", "name": "File", "type": "multipleAttachment"}, + ) + + field_mapping = deepcopy(FIELD_MAPPING) + field_mapping["fldwSc9PqedIhTSqhi3"] = { + "baserow_field": FileField( + id="fldwSc9PqedIhTSqhi3", pk="fldwSc9PqedIhTSqhi3", name="File" + ), + "baserow_field_type": FileFieldType(), + "raw_airtable_column": raw_airtable_table["columns"][2], + "airtable_column_type": None, + } + + import_report = AirtableImportReport() + serialized_view = airtable_view_type.to_serialized_baserow_view( + field_mapping, + ROW_ID_MAPPING, + raw_airtable_table, + raw_airtable_view, + view_data, + AirtableImportConfig(), + import_report, + ) + + assert len(import_report.items) == 0 + assert serialized_view["card_cover_image_field_id"] == "fldwSc9PqedIhTSqhi3" + + +def test_import_gallery_view_with_unknown_cover_column(): + airtable_view_type = airtable_view_type_registry.get("gallery") + + view_data = deepcopy(RAW_AIRTABLE_GALLERY_VIEW_DATA) + view_data["metadata"]["gallery"]["coverColumnId"] = "fldwSc9PqedIhTSqhi3" + + raw_airtable_view = deepcopy(RAW_AIRTABLE_VIEW) + raw_airtable_view["name"] = "Gallery view" + raw_airtable_view["type"] = "gallery" + + raw_airtable_table = deepcopy(RAW_AIRTABLE_TABLE) + + import_report = AirtableImportReport() + serialized_view = airtable_view_type.to_serialized_baserow_view( + FIELD_MAPPING, + ROW_ID_MAPPING, + raw_airtable_table, + raw_airtable_view, + view_data, + AirtableImportConfig(), + import_report, + ) + + assert "card_cover_image_field_id" not in serialized_view + assert len(import_report.items) == 1 + assert import_report.items[0].object_name == "Gallery view" + assert import_report.items[0].scope == SCOPE_VIEW + assert import_report.items[0].table == "Data" + + +def test_import_gallery_view_with_incompatible_cover_column(): + airtable_view_type = airtable_view_type_registry.get("gallery") + + view_data = deepcopy(RAW_AIRTABLE_GALLERY_VIEW_DATA) + view_data["metadata"]["gallery"]["coverColumnId"] = "fldwSc9PqedIhTSqhi2" + + raw_airtable_view = deepcopy(RAW_AIRTABLE_VIEW) + raw_airtable_view["name"] = "Gallery view" + raw_airtable_view["type"] = "gallery" + + raw_airtable_table = deepcopy(RAW_AIRTABLE_TABLE) + + import_report = AirtableImportReport() + serialized_view = airtable_view_type.to_serialized_baserow_view( + FIELD_MAPPING, + ROW_ID_MAPPING, + raw_airtable_table, + raw_airtable_view, + view_data, + AirtableImportConfig(), + import_report, + ) + + assert "card_cover_image_field_id" not in serialized_view + assert len(import_report.items) == 1 + assert import_report.items[0].object_name == "Gallery view" + assert import_report.items[0].scope == SCOPE_VIEW + assert import_report.items[0].table == "Data" + + +def test_import_gallery_view_with_cover_column_type_fit(): + airtable_view_type = airtable_view_type_registry.get("gallery") + + view_data = deepcopy(RAW_AIRTABLE_GALLERY_VIEW_DATA) + view_data["metadata"]["gallery"]["coverColumnId"] = "fldwSc9PqedIhTSqhi3" + view_data["metadata"]["gallery"]["coverFitType"] = "fit" + + raw_airtable_view = deepcopy(RAW_AIRTABLE_VIEW) + raw_airtable_view["name"] = "Gallery view" + raw_airtable_view["type"] = "gallery" + + raw_airtable_table = deepcopy(RAW_AIRTABLE_TABLE) + raw_airtable_table["columns"].append( + {"id": "fldwSc9PqedIhTSqhi3", "name": "File", "type": "multipleAttachment"}, + ) + + field_mapping = deepcopy(FIELD_MAPPING) + field_mapping["fldwSc9PqedIhTSqhi3"] = { + "baserow_field": FileField( + id="fldwSc9PqedIhTSqhi3", pk="fldwSc9PqedIhTSqhi3", name="File" + ), + "baserow_field_type": FileFieldType(), + "raw_airtable_column": raw_airtable_table["columns"][2], + "airtable_column_type": None, + } + + import_report = AirtableImportReport() + serialized_view = airtable_view_type.to_serialized_baserow_view( + field_mapping, + ROW_ID_MAPPING, + raw_airtable_table, + raw_airtable_view, + view_data, + AirtableImportConfig(), + import_report, + ) + + assert len(import_report.items) == 1 + assert import_report.items[0].object_name == "Gallery view" + assert import_report.items[0].scope == SCOPE_VIEW + assert import_report.items[0].table == "Data" + assert serialized_view["card_cover_image_field_id"] == "fldwSc9PqedIhTSqhi3" diff --git a/changelog/entries/unreleased/feature/786_import_airtable_gallery_view.json b/changelog/entries/unreleased/feature/786_import_airtable_gallery_view.json new file mode 100644 index 000000000..d7ac0a81b --- /dev/null +++ b/changelog/entries/unreleased/feature/786_import_airtable_gallery_view.json @@ -0,0 +1,8 @@ +{ + "type": "feature", + "message": "Import Airtable gallery view.", + "domain": "database", + "issue_number": 793, + "bullet_points": [], + "created_at": "2025-03-17" +}