Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ def get_baserow_export_table(self, order: int) -> dict:
empty_serialized_grid_view = grid_view_type.export_serialized(
grid_view,
ImportExportConfig(include_permission_data=False),
None,
{},
None,
None,
)
Expand Down
2 changes: 1 addition & 1 deletion backend/src/baserow/contrib/database/airtable/registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -811,7 +811,7 @@ def to_serialized_baserow_view(
config,
import_report,
)
serialized = view_type.export_serialized(view, config)
serialized = view_type.export_serialized(view, config, {})

return serialized

Expand Down
14 changes: 12 additions & 2 deletions backend/src/baserow/contrib/database/application_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,12 +123,16 @@ def export_tables_serialized(
for table in tables:
fields = table.field_set.all()
serialized_fields = []
specific_fields = []
for f in fields:
field = f.specific
specific_fields.append(field)
field_type = field_type_registry.get_by_model(field)
serialized_fields.append(field_type.export_serialized(field))

table_cache: Dict[str, Any] = {}
table_cache: Dict[str, Any] = {
f"fields_by_id_{table.id}": {f.id: f for f in specific_fields},
}
workspace = table.get_root()
if workspace is not None:
table_cache["workspace_id"] = workspace.id
Expand Down Expand Up @@ -939,10 +943,16 @@ def _import_table_views(

table = serialized_table["_object"]
table_name = serialized_table["name"]
cache: Dict[str, Any] = {}
for serialized_view in serialized_table["views"]:
view_type = view_type_registry.get(serialized_view["type"])
view_type.import_serialized(
table, serialized_view, import_export_config, id_mapping, files_zip
table,
serialized_view,
import_export_config,
id_mapping,
cache,
files_zip,
)
progress.increment(
state=f"{IMPORT_SERIALIZED_IMPORTING_TABLE_STRUCTURE}{table_name}"
Expand Down
48 changes: 46 additions & 2 deletions backend/src/baserow/contrib/database/fields/field_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -4395,7 +4395,7 @@ def get_internal_value_from_db(
) -> int:
return getattr(row, f"{field_name}_id")

def import_serialized_default_value(self, value, id_mapping):
def import_serialized_default_value(self, value, id_mapping, workspace_id, cache):
if isinstance(value, int):
option_mapping = id_mapping.get("database_field_select_options", {})
return option_mapping.get(value, value)
Expand Down Expand Up @@ -4768,7 +4768,7 @@ class MultipleSelectFieldType(
),
}

def import_serialized_default_value(self, value, id_mapping):
def import_serialized_default_value(self, value, id_mapping, workspace_id, cache):
if isinstance(value, list):
option_mapping = id_mapping.get("database_field_select_options", {})
return [
Expand Down Expand Up @@ -7057,6 +7057,50 @@ def set_import_serialized_value(

return through_objects

def export_serialized_default_value(self, value, field, workspace_id, cache):
if not isinstance(value, list):
return value

cache_entry = f"collaborator_id_to_email_export_{workspace_id}"
if cache_entry not in cache:
cache[cache_entry] = dict(
WorkspaceUser.objects.filter(workspace_id=workspace_id).values_list(
"user_id", "user__email"
)
)

id_to_email = cache[cache_entry]
return [
id_to_email[user["id"]]
for user in value
if isinstance(user["id"], int) and user["id"] in id_to_email
]

def import_serialized_default_value(self, value, id_mapping, workspace_id, cache):
if not isinstance(value, list):
return value

cache_key = f"collaborator_email_to_id_import_{workspace_id}"
if cache_key not in cache:
cache[cache_key] = {
workspace_user["user__email"]: workspace_user
for workspace_user in WorkspaceUser.objects.filter(
workspace_id=workspace_id
)
.select_related("user")
.values("user__email", "user__first_name", "user_id")
}

email_to_id = cache[cache_key]
return [
{
"id": email_to_id[email]["user_id"],
"name": email_to_id[email]["user__first_name"],
}
for email in value
if isinstance(email, str) and email in email_to_id
]

def random_value(self, instance, fake, cache):
"""
Selects a random sublist out of the possible collaborators.
Expand Down
27 changes: 27 additions & 0 deletions backend/src/baserow/contrib/database/fields/registries.py
Original file line number Diff line number Diff line change
Expand Up @@ -1253,10 +1253,34 @@ def set_import_serialized_value(

setattr(row, field_name, value)

def export_serialized_default_value(
self,
value: Any,
field: "Field",
workspace_id: int,
cache: Dict,
) -> Any:
"""
Hook that is called when exporting a ViewDefaultValue.value during a
serialized export. Can be used to convert internal IDs to portable
representations (e.g. user IDs to email addresses).

:param value: The raw JSON default value to export.
:param field: The field instance the default value belongs to.
:param workspace_id: The ID of the workspace being exported.
:param cache: A cache dict shared across the export to avoid repeated
queries.
:return: The portable representation of the value.
"""

return value

def import_serialized_default_value(
self,
value: Any,
id_mapping: Dict[str, Any],
workspace_id: int,
cache: Dict,
) -> Any:
"""
Hook that is called just before the ViewDefaultValue.value is set when doing a
Expand All @@ -1265,6 +1289,9 @@ def import_serialized_default_value(

:param value: The raw JSON default value to remap.
:param id_mapping: The map of exported ids to newly created ids.
:param workspace_id: The ID of the workspace being imported into.
:param cache: A cache dict shared across the import to avoid repeated
queries.
:return: The value with remapped IDs.
"""

Expand Down
2 changes: 1 addition & 1 deletion backend/src/baserow/contrib/database/views/handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -1065,7 +1065,7 @@ def duplicate_view(self, user: AbstractUser, original_view: View) -> View:
"database_field_select_options": MirrorDict(),
}
duplicated_view = view_type.import_serialized(
original_view.table, serialized, config, id_mapping
original_view.table, serialized, config, id_mapping, {}
)

if duplicated_view is None:
Expand Down
44 changes: 38 additions & 6 deletions backend/src/baserow/contrib/database/views/registries.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
from rest_framework.serializers import Serializer

from baserow.contrib.database.fields.field_filters import OptionallyAnnotatedQ
from baserow.core.db import specific_iterator
from baserow.core.exceptions import PermissionDenied
from baserow.core.handler import CoreHandler
from baserow.core.models import Workspace, WorkspaceUser
Expand Down Expand Up @@ -230,7 +231,7 @@ def export_serialized(
self,
view: "View",
import_export_config: ImportExportConfig,
cache: Optional[Dict] = None,
cache: Dict,
files_zip: Optional[ExportZipFile] = None,
storage: Optional[Storage] = None,
) -> Dict[str, Any]:
Expand Down Expand Up @@ -338,6 +339,7 @@ def import_serialized(
serialized_values: Dict[str, Any],
import_export_config: ImportExportConfig,
id_mapping: Dict[str, Any],
cache: Dict,
files_zip: Optional[ZipFile] = None,
storage: Optional[Storage] = None,
) -> Optional["View"]:
Expand All @@ -353,6 +355,7 @@ def import_serialized(
import/export process to customize how it works.
:param id_mapping: The map of exported ids to newly created ids that must be
updated when a new instance has been created.
:param cache: A cache to use for storing temporary data.
:param files_zip: A zip file buffer where files related to the export can be
extracted from.
:param storage: The storage where the files can be copied to.
Expand Down Expand Up @@ -549,6 +552,7 @@ def import_serialized(
id_mapping,
files_zip,
storage,
cache,
)

for (
Expand All @@ -564,20 +568,44 @@ def _export_default_row_values(self, view, cache, files_zip, storage):
"""
Exports the default row values for the given view as a serialized dict.
Uses the prefetched ``view_default_values`` related manager when
available to avoid extra queries during batch exports. The raw JSON
value is exported as-is since it is already serializable.
available. Field objects are looked up from
``cache["fields_by_id_{table_id}"]``, which is lazily populated using
``specific_iterator`` if not already set by the caller. The value is
passed through the field type's ``export_serialized_default_value``
hook so that internal IDs can be converted to portable representations.
"""

from baserow.contrib.database.fields.models import Field
from baserow.contrib.database.fields.registries import field_type_registry

default_values = view.view_default_values.all()
if not default_values:
return None
return {}

workspace_id = view.table.database.workspace_id
table_id = view.table_id
cache_key = f"fields_by_id_{table_id}"
if cache_key not in cache:
cache[cache_key] = {
f.id: f
for f in specific_iterator(Field.objects.filter(table_id=table_id))
}
fields_by_id = cache[cache_key]

serialized_values = {}
for default_value in default_values:
value = default_value.value
field = fields_by_id.get(default_value.field_id)
if field is not None and value is not None:
field_type = field_type_registry.get_by_model(field.specific_class)
value = field_type.export_serialized_default_value(
value, field, workspace_id, cache
)

serialized_values[str(default_value.field_id)] = {
"field_id": default_value.field_id,
"enabled": default_value.enabled,
"value": default_value.value,
"value": value,
"function": default_value.function,
"field_type": default_value.field_type,
}
Expand All @@ -592,6 +620,7 @@ def _import_default_row_values(
id_mapping,
files_zip,
storage,
cache,
):
"""
Imports the default row values from a serialized dict using
Expand All @@ -603,6 +632,7 @@ def _import_default_row_values(

from baserow.contrib.database.views.models import ViewDefaultValue

workspace_id = table.database.workspace_id
model = table.get_model()
records = []

Expand All @@ -621,7 +651,9 @@ def _import_default_row_values(

value = default_value_data.get("value")
if value is not None:
value = field_type.import_serialized_default_value(value, id_mapping)
value = field_type.import_serialized_default_value(
value, id_mapping, workspace_id, cache
)

records.append(
ViewDefaultValue(
Expand Down
33 changes: 27 additions & 6 deletions backend/src/baserow/contrib/database/views/view_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ def export_serialized(
self,
grid: View,
import_export_config: ImportExportConfig,
cache: Optional[Dict] = None,
cache: Dict,
files_zip: Optional[ExportZipFile] = None,
storage: Optional[Storage] = None,
):
Expand Down Expand Up @@ -147,6 +147,7 @@ def import_serialized(
serialized_values: Dict[str, Any],
import_export_config: ImportExportConfig,
id_mapping: Dict[str, Any],
cache: Dict,
files_zip: Optional[ZipFile] = None,
storage: Optional[Storage] = None,
) -> Optional[View]:
Expand All @@ -157,7 +158,13 @@ def import_serialized(
serialized_copy = serialized_values.copy()
field_options = serialized_copy.pop("field_options")
grid_view = super().import_serialized(
table, serialized_copy, import_export_config, id_mapping, files_zip, storage
table,
serialized_copy,
import_export_config,
id_mapping,
cache,
files_zip,
storage,
)
if grid_view is not None:
if "database_grid_view_field_options" not in id_mapping:
Expand Down Expand Up @@ -425,7 +432,7 @@ def export_serialized(
self,
gallery: View,
import_export_config: ImportExportConfig,
cache: Optional[Dict] = None,
cache: Dict,
files_zip: Optional[ExportZipFile] = None,
storage: Optional[Storage] = None,
):
Expand Down Expand Up @@ -460,6 +467,7 @@ def import_serialized(
serialized_values: Dict[str, Any],
import_export_config: ImportExportConfig,
id_mapping: Dict[str, Any],
cache: Dict,
files_zip: Optional[ZipFile] = None,
storage: Optional[Storage] = None,
) -> Optional[View]:
Expand All @@ -477,7 +485,13 @@ def import_serialized(
field_options = serialized_copy.pop("field_options")

gallery_view = super().import_serialized(
table, serialized_copy, import_export_config, id_mapping, files_zip, storage
table,
serialized_copy,
import_export_config,
id_mapping,
cache,
files_zip,
storage,
)

if gallery_view is not None:
Expand Down Expand Up @@ -1121,7 +1135,7 @@ def export_serialized(
self,
form: View,
import_export_config: ImportExportConfig,
cache: Optional[Dict] = None,
cache: Dict,
files_zip: Optional[ExportZipFile] = None,
storage: Optional[Storage] = None,
):
Expand Down Expand Up @@ -1210,6 +1224,7 @@ def import_serialized(
serialized_values: Dict[str, Any],
import_export_config: ImportExportConfig,
id_mapping: Dict[str, Any],
cache: Dict,
files_zip: Optional[ZipFile] = None,
storage: Optional[Storage] = None,
) -> Optional[View]:
Expand All @@ -1235,7 +1250,13 @@ def get_file(file):
serialized_copy["logo_image"] = get_file(serialized_copy.pop("logo_image"))
field_options = serialized_copy.pop("field_options")
form_view = super().import_serialized(
table, serialized_copy, import_export_config, id_mapping, files_zip, storage
table,
serialized_copy,
import_export_config,
id_mapping,
cache,
files_zip,
storage,
)

if form_view is not None:
Expand Down
Loading
Loading