Skip to content

Commit 0596903

Browse files
authored
[1/6] View level permissions - Plumbing (baserow#4069)
1 parent ad495e4 commit 0596903

File tree

53 files changed

+1120
-288
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

53 files changed

+1120
-288
lines changed

backend/src/baserow/api/serializers.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ def to_internal_value(self, data):
8585

8686
natural_key = super().to_internal_value(data)
8787
try:
88-
return self._model.objects.get_by_natural_key(*natural_key)
88+
return self.get_queryset().get_by_natural_key(*natural_key)
8989
except self._model.DoesNotExist as e:
9090
if self._custom_does_not_exist_exception_class:
9191
raise self._custom_does_not_exist_exception_class(
@@ -94,6 +94,9 @@ def to_internal_value(self, data):
9494
else:
9595
raise e
9696

97+
def get_queryset(self):
98+
return self._model.objects
99+
97100

98101
class CommaSeparatedIntegerValuesField(serializers.Field):
99102
"""A serializer field that accepts a CSV string containing a list of integers."""

backend/src/baserow/contrib/database/airtable/import_report.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
from baserow.contrib.database.views.models import GridView
1313
from baserow.contrib.database.views.registries import view_type_registry
1414
from baserow.core.constants import BASEROW_COLORS
15+
from baserow.core.registries import ImportExportConfig
1516

1617
REPORT_TABLE_ID = "report"
1718
REPORT_TABLE_NAME = "Airtable import report"
@@ -99,7 +100,11 @@ def get_baserow_export_table(self, order: int) -> dict:
99100
grid_view.get_field_options = lambda *args, **kwargs: []
100101
grid_view_type = view_type_registry.get_by_model(grid_view)
101102
empty_serialized_grid_view = grid_view_type.export_serialized(
102-
grid_view, None, None, None
103+
grid_view,
104+
ImportExportConfig(include_permission_data=False),
105+
None,
106+
None,
107+
None,
103108
)
104109
empty_serialized_grid_view["id"] = 0
105110
exported_views = [empty_serialized_grid_view]

backend/src/baserow/contrib/database/airtable/registry.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -811,7 +811,7 @@ def to_serialized_baserow_view(
811811
config,
812812
import_report,
813813
)
814-
serialized = view_type.export_serialized(view)
814+
serialized = view_type.export_serialized(view, config)
815815

816816
return serialized
817817

backend/src/baserow/contrib/database/application_types.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,9 @@ def export_tables_serialized(
135135
view = v.specific
136136
view_type = view_type_registry.get_by_model(view)
137137
serialized_views.append(
138-
view_type.export_serialized(view, table_cache, files_zip, storage)
138+
view_type.export_serialized(
139+
view, import_export_config, table_cache, files_zip, storage
140+
)
139141
)
140142

141143
serialized_rows = []
@@ -564,7 +566,9 @@ def import_tables_serialized(
564566
# Now that the all tables and fields exist, we can create the views and create
565567
# the table schema in the database.
566568
for serialized_table in serialized_tables:
567-
self._import_table_views(serialized_table, id_mapping, files_zip, progress)
569+
self._import_table_views(
570+
serialized_table, import_export_config, id_mapping, files_zip, progress
571+
)
568572
self._create_table_schema(
569573
serialized_table, already_created_through_table_names
570574
)
@@ -910,6 +914,7 @@ def _create_table_schema(
910914
def _import_table_views(
911915
self,
912916
serialized_table: Dict[str, Any],
917+
import_export_config: ImportExportConfig,
913918
id_mapping: Dict[str, Any],
914919
files_zip: Optional[ZipFile] = None,
915920
progress: Optional[ChildProgressBuilder] = None,
@@ -929,7 +934,9 @@ def _import_table_views(
929934
table_name = serialized_table["name"]
930935
for serialized_view in serialized_table["views"]:
931936
view_type = view_type_registry.get(serialized_view["type"])
932-
view_type.import_serialized(table, serialized_view, id_mapping, files_zip)
937+
view_type.import_serialized(
938+
table, serialized_view, import_export_config, id_mapping, files_zip
939+
)
933940
progress.increment(
934941
state=f"{IMPORT_SERIALIZED_IMPORTING_TABLE_STRUCTURE}{table_name}"
935942
)

backend/src/baserow/contrib/database/views/handler.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@
8888
from baserow.core.exceptions import PermissionDenied
8989
from baserow.core.handler import CoreHandler
9090
from baserow.core.models import Workspace
91+
from baserow.core.registries import ImportExportConfig
9192
from baserow.core.telemetry.utils import baserow_trace_methods
9293
from baserow.core.trash.handler import TrashHandler
9394
from baserow.core.utils import (
@@ -898,6 +899,7 @@ def create_view(
898899
)
899900

900901
view_type.view_created(view=instance)
902+
view_ownership_type.view_created(user=user, view=instance, workspace=workspace)
901903
view_created.send(self, view=instance, user=user, type_name=type_name)
902904

903905
return instance
@@ -938,12 +940,18 @@ def duplicate_view(self, user: AbstractUser, original_view: View) -> View:
938940

939941
view_type = view_type_registry.get_by_model(original_view)
940942

943+
config = ImportExportConfig(
944+
include_permission_data=True,
945+
reduce_disk_space_usage=False,
946+
is_duplicate=True,
947+
)
948+
941949
cache = {
942950
"workspace_id": workspace.id,
943951
}
944952

945953
# Use export/import to duplicate the view easily
946-
serialized = view_type.export_serialized(original_view, cache)
954+
serialized = view_type.export_serialized(original_view, config, cache)
947955

948956
# Change the name of the view
949957
serialized["name"] = self.find_unused_view_name(
@@ -967,7 +975,7 @@ def duplicate_view(self, user: AbstractUser, original_view: View) -> View:
967975
"database_field_select_options": MirrorDict(),
968976
}
969977
duplicated_view = view_type.import_serialized(
970-
original_view.table, serialized, id_mapping
978+
original_view.table, serialized, config, id_mapping
971979
)
972980

973981
if duplicated_view is None:

backend/src/baserow/contrib/database/views/registries.py

Lines changed: 49 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,11 @@
2525
from baserow.core.exceptions import PermissionDenied
2626
from baserow.core.handler import CoreHandler
2727
from baserow.core.models import Workspace, WorkspaceUser
28-
from baserow.core.registries import OperationType
28+
from baserow.core.registries import (
29+
ImportExportConfig,
30+
OperationType,
31+
serialization_processor_registry,
32+
)
2933
from baserow.core.registry import (
3034
APIUrlsInstanceMixin,
3135
APIUrlsRegistryMixin,
@@ -219,6 +223,7 @@ def __init__(self, *args, **kwargs):
219223
def export_serialized(
220224
self,
221225
view: "View",
226+
import_export_config: ImportExportConfig,
222227
cache: Optional[Dict] = None,
223228
files_zip: Optional[ExportZipFile] = None,
224229
storage: Optional[Storage] = None,
@@ -228,6 +233,9 @@ def export_serialized(
228233
`import_serialized` method. This dict is also JSON serializable.
229234
230235
:param view: The view instance that must be exported.
236+
:param import_export_config: provides configuration options for the
237+
import/export process to customize how it works.
238+
:param cache: A cache to use for storing temporary data.
231239
:param files_zip: A zip file buffer where the files related to the export
232240
must be copied into.
233241
:param storage: The storage where the files can be loaded from.
@@ -298,12 +306,24 @@ def export_serialized(
298306
if self.can_share:
299307
serialized["public"] = view.public
300308

309+
# It could be that there is no `table` related to the view when doing an
310+
# Airtable export, for example. That means it's not part of a workspace, so we
311+
# can't enhance the export with the `serialization_processor_registry`.
312+
if view.table_id is not None:
313+
for serialized_structure in serialization_processor_registry.get_all():
314+
extra_data = serialized_structure.export_serialized(
315+
view.table.database.workspace, view, import_export_config
316+
)
317+
if extra_data is not None:
318+
serialized.update(**extra_data)
319+
301320
return serialized
302321

303322
def import_serialized(
304323
self,
305324
table: "Table",
306325
serialized_values: Dict[str, Any],
326+
import_export_config: ImportExportConfig,
307327
id_mapping: Dict[str, Any],
308328
files_zip: Optional[ZipFile] = None,
309329
storage: Optional[Storage] = None,
@@ -316,6 +336,8 @@ def import_serialized(
316336
:param table: The table where the view should be added to.
317337
:param serialized_values: The exported serialized view values that need to
318338
be imported.
339+
:param import_export_config: provides configuration options for the
340+
import/export process to customize how it works.
319341
:param id_mapping: The map of exported ids to newly created ids that must be
320342
updated when a new instance has been created.
321343
:param files_zip: A zip file buffer where files related to the export can be
@@ -399,7 +421,15 @@ def import_serialized(
399421
decorations = (
400422
serialized_copy.pop("decorations", []) if self.can_decorate else []
401423
)
402-
view = self.model_class.objects.create(table=table, **serialized_copy)
424+
425+
view = self.model_class(table=table)
426+
# Only set the properties that are actually accepted by the view type's model
427+
# class.
428+
for key, value in serialized_copy.items():
429+
if hasattr(view, key):
430+
setattr(view, key, value)
431+
view.save()
432+
403433
id_mapping["database_views"][view_id] = view.id
404434

405435
if self.can_filter:
@@ -493,6 +523,13 @@ def import_serialized(
493523
view_decoration_id
494524
] = view_decoration_object.id
495525

526+
for (
527+
serialized_structure_processor
528+
) in serialization_processor_registry.get_all():
529+
serialized_structure_processor.import_serialized(
530+
table.database.workspace, view, serialized_copy, import_export_config
531+
)
532+
496533
return view
497534

498535
def get_visible_fields_and_model(
@@ -1406,6 +1443,16 @@ class (f. ex. `CollaborativeViewOwnershipType`,
14061443

14071444
raise PermissionDenied()
14081445

1446+
def view_created(self, user: AbstractUser, view: "View", workspace: Workspace):
1447+
"""
1448+
Hook that is called after a view is created. This can be used to introduce
1449+
additional permissions checks, for example.
1450+
1451+
:param user: The user that created the view.
1452+
:param view: The view that was created.
1453+
:param workspace: The workspace where the view was created in.
1454+
"""
1455+
14091456

14101457
class ViewOwnershipTypeRegistry(Registry):
14111458
"""

backend/src/baserow/contrib/database/views/view_types.py

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
from baserow.contrib.database.views.registries import view_aggregation_type_registry
4949
from baserow.core.handler import CoreHandler
5050
from baserow.core.import_export.utils import file_chunk_generator
51+
from baserow.core.registries import ImportExportConfig
5152
from baserow.core.storage import ExportZipFile
5253
from baserow.core.user_files.handler import UserFileHandler
5354
from baserow.core.user_files.models import UserFile
@@ -108,6 +109,7 @@ def get_api_urls(self):
108109
def export_serialized(
109110
self,
110111
grid: View,
112+
import_export_config: ImportExportConfig,
111113
cache: Optional[Dict] = None,
112114
files_zip: Optional[ExportZipFile] = None,
113115
storage: Optional[Storage] = None,
@@ -116,7 +118,9 @@ def export_serialized(
116118
Adds the serialized grid view options to the exported dict.
117119
"""
118120

119-
serialized = super().export_serialized(grid, cache, files_zip, storage)
121+
serialized = super().export_serialized(
122+
grid, import_export_config, cache, files_zip, storage
123+
)
120124
serialized["row_identifier_type"] = grid.row_identifier_type
121125
serialized["row_height_size"] = grid.row_height_size
122126

@@ -141,6 +145,7 @@ def import_serialized(
141145
self,
142146
table: Table,
143147
serialized_values: Dict[str, Any],
148+
import_export_config: ImportExportConfig,
144149
id_mapping: Dict[str, Any],
145150
files_zip: Optional[ZipFile] = None,
146151
storage: Optional[Storage] = None,
@@ -152,7 +157,7 @@ def import_serialized(
152157
serialized_copy = serialized_values.copy()
153158
field_options = serialized_copy.pop("field_options")
154159
grid_view = super().import_serialized(
155-
table, serialized_copy, id_mapping, files_zip, storage
160+
table, serialized_copy, import_export_config, id_mapping, files_zip, storage
156161
)
157162
if grid_view is not None:
158163
if "database_grid_view_field_options" not in id_mapping:
@@ -417,6 +422,7 @@ def after_fields_type_change(self, fields):
417422
def export_serialized(
418423
self,
419424
gallery: View,
425+
import_export_config: ImportExportConfig,
420426
cache: Optional[Dict] = None,
421427
files_zip: Optional[ExportZipFile] = None,
422428
storage: Optional[Storage] = None,
@@ -425,7 +431,9 @@ def export_serialized(
425431
Adds the serialized gallery view options to the exported dict.
426432
"""
427433

428-
serialized = super().export_serialized(gallery, cache, files_zip, storage)
434+
serialized = super().export_serialized(
435+
gallery, import_export_config, cache, files_zip, storage
436+
)
429437

430438
if gallery.card_cover_image_field_id:
431439
serialized["card_cover_image_field_id"] = gallery.card_cover_image_field_id
@@ -448,6 +456,7 @@ def import_serialized(
448456
self,
449457
table: Table,
450458
serialized_values: Dict[str, Any],
459+
import_export_config: ImportExportConfig,
451460
id_mapping: Dict[str, Any],
452461
files_zip: Optional[ZipFile] = None,
453462
storage: Optional[Storage] = None,
@@ -466,7 +475,7 @@ def import_serialized(
466475
field_options = serialized_copy.pop("field_options")
467476

468477
gallery_view = super().import_serialized(
469-
table, serialized_copy, id_mapping, files_zip, storage
478+
table, serialized_copy, import_export_config, id_mapping, files_zip, storage
470479
)
471480

472481
if gallery_view is not None:
@@ -1110,6 +1119,7 @@ def _update_field_options_allowed_select_options(
11101119
def export_serialized(
11111120
self,
11121121
form: View,
1122+
import_export_config: ImportExportConfig,
11131123
cache: Optional[Dict] = None,
11141124
files_zip: Optional[ExportZipFile] = None,
11151125
storage: Optional[Storage] = None,
@@ -1118,7 +1128,9 @@ def export_serialized(
11181128
Adds the serialized form view options to the exported dict.
11191129
"""
11201130

1121-
serialized = super().export_serialized(form, cache, files_zip, storage)
1131+
serialized = super().export_serialized(
1132+
form, import_export_config, cache, files_zip, storage
1133+
)
11221134

11231135
def add_user_file(user_file):
11241136
if not user_file:
@@ -1195,6 +1207,7 @@ def import_serialized(
11951207
self,
11961208
table: Table,
11971209
serialized_values: Dict[str, Any],
1210+
import_export_config: ImportExportConfig,
11981211
id_mapping: Dict[str, Any],
11991212
files_zip: Optional[ZipFile] = None,
12001213
storage: Optional[Storage] = None,
@@ -1221,7 +1234,7 @@ def get_file(file):
12211234
serialized_copy["logo_image"] = get_file(serialized_copy.pop("logo_image"))
12221235
field_options = serialized_copy.pop("field_options")
12231236
form_view = super().import_serialized(
1224-
table, serialized_copy, id_mapping, files_zip, storage
1237+
table, serialized_copy, import_export_config, id_mapping, files_zip, storage
12251238
)
12261239

12271240
if form_view is not None:

backend/src/baserow/core/types.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,10 @@
44
from django.contrib.auth.models import AbstractUser # noqa: F401
55
from django.contrib.auth.models import AnonymousUser # noqa: F401
66

7-
from baserow.contrib.automation.models import Automation
8-
from baserow.contrib.builder.models import Builder
9-
from baserow.contrib.dashboard.models import Dashboard
10-
from baserow.contrib.database.models import Database, Table
7+
from baserow.contrib.automation.models import Automation # noqa: F401
8+
from baserow.contrib.builder.models import Builder # noqa: F401
9+
from baserow.contrib.dashboard.models import Dashboard # noqa: F401
10+
from baserow.contrib.database.models import Database, Table, View # noqa: F401
1111

1212
# A scope object needs to have a related registered ScopeObjectType
1313
ScopeObject = Any
@@ -24,7 +24,7 @@
2424

2525
# Objects which can be exported and imported in a `SerializationProcessorType`.
2626
SerializationProcessorScope = Union[
27-
"Database", "Table", "Builder", "Dashboard", "Automation"
27+
"Database", "Table", "View", "Builder", "Dashboard", "Automation"
2828
]
2929

3030

0 commit comments

Comments
 (0)