mirror of
https://gitlab.com/bramw/baserow.git
synced 2025-04-10 07:37:30 +00:00
adding ability to enhance workspace queryset
This commit is contained in:
parent
8a679d0550
commit
09c22ae0af
9 changed files with 142 additions and 64 deletions
backend/src/baserow
api/applications
contrib/database
api
fields
formula/types
core
|
@ -43,7 +43,7 @@ from baserow.core.job_types import DuplicateApplicationJobType
|
||||||
from baserow.core.jobs.exceptions import MaxJobCountExceeded
|
from baserow.core.jobs.exceptions import MaxJobCountExceeded
|
||||||
from baserow.core.jobs.handler import JobHandler
|
from baserow.core.jobs.handler import JobHandler
|
||||||
from baserow.core.jobs.registries import job_type_registry
|
from baserow.core.jobs.registries import job_type_registry
|
||||||
from baserow.core.models import Application, Workspace
|
from baserow.core.models import Application
|
||||||
from baserow.core.operations import CreateApplicationsWorkspaceOperationType
|
from baserow.core.operations import CreateApplicationsWorkspaceOperationType
|
||||||
from baserow.core.service import CoreService
|
from baserow.core.service import CoreService
|
||||||
from baserow.core.trash.exceptions import CannotDeleteAlreadyDeletedItem
|
from baserow.core.trash.exceptions import CannotDeleteAlreadyDeletedItem
|
||||||
|
@ -81,13 +81,15 @@ class AllApplicationsView(APIView):
|
||||||
returned.
|
returned.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
workspaces = Workspace.objects.filter(users=request.user).prefetch_related(
|
workspaces = CoreService().list_workspaces(request.user)
|
||||||
"workspaceuser_set", "template_set"
|
|
||||||
)
|
|
||||||
applications_qs = Application.objects.none()
|
applications_qs = Application.objects.none()
|
||||||
for workspace in workspaces:
|
for workspace in workspaces:
|
||||||
|
workspace_applications_qs = CoreService().list_applications_in_workspace(
|
||||||
|
request.user, workspace
|
||||||
|
)
|
||||||
applications_qs = applications_qs.union(
|
applications_qs = applications_qs.union(
|
||||||
CoreService().list_applications_in_workspace(request.user, workspace)
|
workspace_applications_qs.order_by(), all=True
|
||||||
)
|
)
|
||||||
|
|
||||||
data = [
|
data = [
|
||||||
|
@ -148,7 +150,6 @@ class ApplicationsView(APIView):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
workspace = CoreService().get_workspace(request.user, workspace_id)
|
workspace = CoreService().get_workspace(request.user, workspace_id)
|
||||||
|
|
||||||
applications = CoreService().list_applications_in_workspace(
|
applications = CoreService().list_applications_in_workspace(
|
||||||
request.user, workspace
|
request.user, workspace
|
||||||
)
|
)
|
||||||
|
|
|
@ -339,6 +339,37 @@ class CollaboratorSerializer(serializers.Serializer):
|
||||||
name = serializers.CharField(source="first_name", read_only=True)
|
name = serializers.CharField(source="first_name", read_only=True)
|
||||||
|
|
||||||
|
|
||||||
|
class AvailableCollaboratorsSerializer(serializers.ListField):
|
||||||
|
def __init__(self, **kwargs):
|
||||||
|
kwargs["child"] = CollaboratorSerializer()
|
||||||
|
kwargs["read_only"] = True
|
||||||
|
kwargs["source"] = "*"
|
||||||
|
kwargs["help_text"] = "A list of all the available collaborators."
|
||||||
|
|
||||||
|
super().__init__(**kwargs)
|
||||||
|
|
||||||
|
def get_attribute(self, instance):
|
||||||
|
return super().get_attribute(instance)
|
||||||
|
|
||||||
|
def to_representation(self, instance):
|
||||||
|
field_type = instance.get_type()
|
||||||
|
if not field_type.can_represent_collaborators(instance):
|
||||||
|
return []
|
||||||
|
|
||||||
|
workspace = instance.table.database.workspace
|
||||||
|
if not hasattr(workspace, "available_collaborators"):
|
||||||
|
setattr(
|
||||||
|
workspace,
|
||||||
|
"available_collaborators",
|
||||||
|
workspace.users.order_by("first_name"),
|
||||||
|
)
|
||||||
|
|
||||||
|
return [
|
||||||
|
CollaboratorSerializer(user).data
|
||||||
|
for user in workspace.available_collaborators
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
class DuplicateFieldParamsSerializer(serializers.Serializer):
|
class DuplicateFieldParamsSerializer(serializers.Serializer):
|
||||||
duplicate_data = serializers.BooleanField(
|
duplicate_data = serializers.BooleanField(
|
||||||
default=False, help_text="Indicates whether the data should be duplicated."
|
default=False, help_text="Indicates whether the data should be duplicated."
|
||||||
|
|
|
@ -39,21 +39,3 @@ class BaserowFormulaSelectOptionsSerializer(serializers.ListField):
|
||||||
return [self.child.to_representation(item) for item in select_options]
|
return [self.child.to_representation(item) for item in select_options]
|
||||||
else:
|
else:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
|
||||||
class BaserowFormulaCollaboratorsSerializer(serializers.ListField):
|
|
||||||
def get_attribute(self, instance):
|
|
||||||
return instance
|
|
||||||
|
|
||||||
def to_representation(self, field):
|
|
||||||
field_type = field_type_registry.get_by_model(field)
|
|
||||||
|
|
||||||
# Available collaborators are needed for view filters in the frontend,
|
|
||||||
# but let's avoid the potentially slow query if not required.
|
|
||||||
if field_type.can_represent_collaborators(field):
|
|
||||||
available_collaborators = field.table.database.workspace.users.all()
|
|
||||||
return [
|
|
||||||
self.child.to_representation(item) for item in available_collaborators
|
|
||||||
]
|
|
||||||
else:
|
|
||||||
return []
|
|
||||||
|
|
|
@ -70,6 +70,7 @@ from baserow.contrib.database.api.fields.errors import (
|
||||||
ERROR_WITH_FORMULA,
|
ERROR_WITH_FORMULA,
|
||||||
)
|
)
|
||||||
from baserow.contrib.database.api.fields.serializers import (
|
from baserow.contrib.database.api.fields.serializers import (
|
||||||
|
AvailableCollaboratorsSerializer,
|
||||||
BaserowBooleanField,
|
BaserowBooleanField,
|
||||||
CollaboratorSerializer,
|
CollaboratorSerializer,
|
||||||
DurationFieldSerializer,
|
DurationFieldSerializer,
|
||||||
|
@ -1506,15 +1507,16 @@ class LastModifiedByFieldType(ReadOnlyFieldType):
|
||||||
|
|
||||||
source_field_name = "last_modified_by"
|
source_field_name = "last_modified_by"
|
||||||
model_field_kwargs = {"sync_with": "last_modified_by"}
|
model_field_kwargs = {"sync_with": "last_modified_by"}
|
||||||
|
request_serializer_field_names = []
|
||||||
|
request_serializer_field_overrides = {}
|
||||||
serializer_field_names = ["available_collaborators"]
|
serializer_field_names = ["available_collaborators"]
|
||||||
serializer_field_overrides = {
|
serializer_field_overrides = {
|
||||||
"available_collaborators": serializers.ListField(
|
"available_collaborators": AvailableCollaboratorsSerializer(),
|
||||||
child=CollaboratorSerializer(),
|
|
||||||
read_only=True,
|
|
||||||
source="table.database.workspace.users.all",
|
|
||||||
),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def can_represent_collaborators(self, field):
|
||||||
|
return True
|
||||||
|
|
||||||
def get_model_field(self, instance, **kwargs):
|
def get_model_field(self, instance, **kwargs):
|
||||||
kwargs["null"] = True
|
kwargs["null"] = True
|
||||||
kwargs["blank"] = True
|
kwargs["blank"] = True
|
||||||
|
@ -1731,15 +1733,16 @@ class CreatedByFieldType(ReadOnlyFieldType):
|
||||||
|
|
||||||
source_field_name = "created_by"
|
source_field_name = "created_by"
|
||||||
model_field_kwargs = {"sync_with_add": "created_by"}
|
model_field_kwargs = {"sync_with_add": "created_by"}
|
||||||
|
request_serializer_field_names = []
|
||||||
|
request_serializer_field_overrides = {}
|
||||||
serializer_field_names = ["available_collaborators"]
|
serializer_field_names = ["available_collaborators"]
|
||||||
serializer_field_overrides = {
|
serializer_field_overrides = {
|
||||||
"available_collaborators": serializers.ListField(
|
"available_collaborators": AvailableCollaboratorsSerializer(),
|
||||||
child=CollaboratorSerializer(),
|
|
||||||
read_only=True,
|
|
||||||
source="table.database.workspace.users.all",
|
|
||||||
),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def can_represent_collaborators(self, field):
|
||||||
|
return True
|
||||||
|
|
||||||
def get_model_field(self, instance, **kwargs):
|
def get_model_field(self, instance, **kwargs):
|
||||||
kwargs["null"] = True
|
kwargs["null"] = True
|
||||||
kwargs["blank"] = True
|
kwargs["blank"] = True
|
||||||
|
@ -6108,18 +6111,24 @@ class MultipleCollaboratorsFieldType(
|
||||||
model_class = MultipleCollaboratorsField
|
model_class = MultipleCollaboratorsField
|
||||||
can_get_unique_values = False
|
can_get_unique_values = False
|
||||||
allowed_fields = ["notify_user_when_added"]
|
allowed_fields = ["notify_user_when_added"]
|
||||||
serializer_field_names = ["available_collaborators", "notify_user_when_added"]
|
request_serializer_field_names = ["notify_user_when_added"]
|
||||||
serializer_field_overrides = {
|
request_serializer_field_overrides = {
|
||||||
"available_collaborators": serializers.ListField(
|
|
||||||
child=CollaboratorSerializer(),
|
|
||||||
read_only=True,
|
|
||||||
source="table.database.workspace.users.all",
|
|
||||||
),
|
|
||||||
"notify_user_when_added": serializers.BooleanField(required=False),
|
"notify_user_when_added": serializers.BooleanField(required=False),
|
||||||
}
|
}
|
||||||
|
serializer_field_names = [
|
||||||
|
"available_collaborators",
|
||||||
|
*request_serializer_field_names,
|
||||||
|
]
|
||||||
|
serializer_field_overrides = {
|
||||||
|
"available_collaborators": AvailableCollaboratorsSerializer(),
|
||||||
|
**request_serializer_field_overrides,
|
||||||
|
}
|
||||||
is_many_to_many_field = True
|
is_many_to_many_field = True
|
||||||
_can_group_by = True
|
_can_group_by = True
|
||||||
|
|
||||||
|
def can_represent_collaborators(self, field):
|
||||||
|
return True
|
||||||
|
|
||||||
def get_serializer_field(self, instance, **kwargs):
|
def get_serializer_field(self, instance, **kwargs):
|
||||||
required = kwargs.pop("required", False)
|
required = kwargs.pop("required", False)
|
||||||
field_serializer = CollaboratorSerializer(
|
field_serializer = CollaboratorSerializer(
|
||||||
|
|
|
@ -208,6 +208,7 @@ class FieldHandler(metaclass=baserow_trace_methods(tracer)):
|
||||||
queryset=User.objects.filter(profile__to_be_deleted=False).order_by(
|
queryset=User.objects.filter(profile__to_be_deleted=False).order_by(
|
||||||
"first_name"
|
"first_name"
|
||||||
),
|
),
|
||||||
|
to_attr="available_collaborators",
|
||||||
),
|
),
|
||||||
"select_options",
|
"select_options",
|
||||||
)
|
)
|
||||||
|
|
|
@ -1345,11 +1345,10 @@ class BaserowFormulaArrayType(
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_serializer_field_overrides(cls):
|
def get_serializer_field_overrides(cls):
|
||||||
from baserow.contrib.database.api.fields.serializers import (
|
from baserow.contrib.database.api.fields.serializers import (
|
||||||
CollaboratorSerializer,
|
AvailableCollaboratorsSerializer,
|
||||||
SelectOptionSerializer,
|
SelectOptionSerializer,
|
||||||
)
|
)
|
||||||
from baserow.contrib.database.api.formula.serializers import (
|
from baserow.contrib.database.api.formula.serializers import (
|
||||||
BaserowFormulaCollaboratorsSerializer,
|
|
||||||
BaserowFormulaSelectOptionsSerializer,
|
BaserowFormulaSelectOptionsSerializer,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -1360,11 +1359,8 @@ class BaserowFormulaArrayType(
|
||||||
allow_null=True,
|
allow_null=True,
|
||||||
read_only=True,
|
read_only=True,
|
||||||
),
|
),
|
||||||
"available_collaborators": BaserowFormulaCollaboratorsSerializer(
|
"available_collaborators": AvailableCollaboratorsSerializer(
|
||||||
child=CollaboratorSerializer(),
|
required=False, allow_null=True
|
||||||
required=False,
|
|
||||||
allow_null=True,
|
|
||||||
read_only=True,
|
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1779,18 +1775,12 @@ class BaserowFormulaMultipleCollaboratorsType(BaserowJSONBObjectBaseType):
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_serializer_field_overrides(cls):
|
def get_serializer_field_overrides(cls):
|
||||||
from baserow.contrib.database.api.fields.serializers import (
|
from baserow.contrib.database.api.fields.serializers import (
|
||||||
CollaboratorSerializer,
|
AvailableCollaboratorsSerializer,
|
||||||
)
|
|
||||||
from baserow.contrib.database.api.formula.serializers import (
|
|
||||||
BaserowFormulaCollaboratorsSerializer,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"available_collaborators": BaserowFormulaCollaboratorsSerializer(
|
"available_collaborators": AvailableCollaboratorsSerializer(
|
||||||
child=CollaboratorSerializer(),
|
allow_null=True, required=False
|
||||||
allow_null=True,
|
|
||||||
required=False,
|
|
||||||
read_only=True,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -521,6 +521,41 @@ class CoreHandler(metaclass=baserow_trace_methods(tracer)):
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def list_user_workspaces(
|
||||||
|
self, user: AbstractUser, base_queryset: QuerySet[Workspace] = None
|
||||||
|
) -> QuerySet[Workspace]:
|
||||||
|
"""
|
||||||
|
Returns a queryset of all workspaces the user is in.
|
||||||
|
|
||||||
|
:param user: The user for which to get the workspaces.
|
||||||
|
:param base_queryset: The base queryset from where to select the workspaces
|
||||||
|
object. This can for example be used to do a `prefetch_related`.
|
||||||
|
:return: A queryset of all workspaces the user is in.
|
||||||
|
"""
|
||||||
|
|
||||||
|
workspace_qs = self.get_enhanced_workspace_queryset(base_queryset)
|
||||||
|
return workspace_qs.filter(workspaceuser__user=user)
|
||||||
|
|
||||||
|
def get_enhanced_workspace_queryset(
|
||||||
|
self, queryset: QuerySet[Workspace] | None = None
|
||||||
|
) -> QuerySet[Workspace]:
|
||||||
|
"""
|
||||||
|
Enhances the workspace queryset with additional prefetches and filters based on
|
||||||
|
the plugins registered in the plugin registry.
|
||||||
|
|
||||||
|
:param queryset: The Workspace queryset to enhance.
|
||||||
|
:return: The enhanced queryset.
|
||||||
|
"""
|
||||||
|
|
||||||
|
if queryset is None:
|
||||||
|
queryset = Workspace.objects.all()
|
||||||
|
|
||||||
|
queryset = queryset.prefetch_related("workspaceuser_set", "template_set")
|
||||||
|
|
||||||
|
for plugin in plugin_registry.registry.values():
|
||||||
|
queryset = plugin.enhance_workspace_queryset(queryset)
|
||||||
|
return queryset
|
||||||
|
|
||||||
def get_workspace(
|
def get_workspace(
|
||||||
self, workspace_id: int, base_queryset: QuerySet = None
|
self, workspace_id: int, base_queryset: QuerySet = None
|
||||||
) -> Workspace:
|
) -> Workspace:
|
||||||
|
@ -535,11 +570,10 @@ class CoreHandler(metaclass=baserow_trace_methods(tracer)):
|
||||||
:return: The requested workspace instance of the provided id.
|
:return: The requested workspace instance of the provided id.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if base_queryset is None:
|
workspace_qs = self.get_enhanced_workspace_queryset(base_queryset)
|
||||||
base_queryset = Workspace.objects
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
workspace = base_queryset.get(id=workspace_id)
|
workspace = workspace_qs.get(id=workspace_id)
|
||||||
except Workspace.DoesNotExist:
|
except Workspace.DoesNotExist:
|
||||||
raise WorkspaceDoesNotExist(
|
raise WorkspaceDoesNotExist(
|
||||||
f"The workspace with id {workspace_id} does not exist."
|
f"The workspace with id {workspace_id} does not exist."
|
||||||
|
|
|
@ -215,8 +215,22 @@ class Plugin(APIUrlsInstanceMixin, Instance):
|
||||||
:type user: User
|
:type user: User
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
def enhance_workspace_queryset(
|
||||||
|
self, queryset: QuerySet["Workspace"]
|
||||||
|
) -> QuerySet["Workspace"]:
|
||||||
|
"""
|
||||||
|
Optimizes the queryset by adding select and prefetch related statements.
|
||||||
|
This reduces queries and improves performance when accessing workspace-related
|
||||||
|
models in plugin views or methods.
|
||||||
|
|
||||||
class PluginRegistry(APIUrlsRegistryMixin, Registry):
|
:param queryset: The queryset to optimize.
|
||||||
|
:return: The optimized queryset.
|
||||||
|
"""
|
||||||
|
|
||||||
|
return queryset
|
||||||
|
|
||||||
|
|
||||||
|
class PluginRegistry(APIUrlsRegistryMixin, Registry[Plugin]):
|
||||||
"""
|
"""
|
||||||
With the plugin registry it is possible to register new plugins. A plugin is an
|
With the plugin registry it is possible to register new plugins. A plugin is an
|
||||||
abstraction made specifically for Baserow. It allows a plugin developer to
|
abstraction made specifically for Baserow. It allows a plugin developer to
|
||||||
|
|
|
@ -8,6 +8,7 @@ from baserow.core.handler import CoreHandler
|
||||||
from baserow.core.models import Application, Workspace
|
from baserow.core.models import Application, Workspace
|
||||||
from baserow.core.operations import (
|
from baserow.core.operations import (
|
||||||
ListApplicationsWorkspaceOperationType,
|
ListApplicationsWorkspaceOperationType,
|
||||||
|
ListWorkspacesOperationType,
|
||||||
ReadApplicationOperationType,
|
ReadApplicationOperationType,
|
||||||
ReadWorkspaceOperationType,
|
ReadWorkspaceOperationType,
|
||||||
)
|
)
|
||||||
|
@ -18,11 +19,26 @@ class CoreService:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.handler = CoreHandler()
|
self.handler = CoreHandler()
|
||||||
|
|
||||||
def _filter_specific_queryset(self, user: AbstractUser, workspace: Workspace):
|
def _enhance_and_filter_application_queryset(
|
||||||
|
self, user: AbstractUser, workspace: Workspace
|
||||||
|
):
|
||||||
return lambda model, queryset: application_type_registry.get_by_model(
|
return lambda model, queryset: application_type_registry.get_by_model(
|
||||||
model
|
model
|
||||||
).enhance_and_filter_queryset(queryset, user, workspace)
|
).enhance_and_filter_queryset(queryset, user, workspace)
|
||||||
|
|
||||||
|
def list_workspaces(self, user: AbstractUser) -> QuerySet[Workspace]:
|
||||||
|
"""
|
||||||
|
Get a list of all the workspaces the user has access to.
|
||||||
|
|
||||||
|
:param user: The user trying to access the workspaces
|
||||||
|
:return: A list of workspaces.
|
||||||
|
"""
|
||||||
|
|
||||||
|
workspace_qs = self.handler.list_user_workspaces(user)
|
||||||
|
return self.handler.filter_queryset(
|
||||||
|
user, ListWorkspacesOperationType.type, workspace_qs
|
||||||
|
)
|
||||||
|
|
||||||
def get_workspace(self, user: AbstractUser, workspace_id: int) -> Workspace:
|
def get_workspace(self, user: AbstractUser, workspace_id: int) -> Workspace:
|
||||||
"""
|
"""
|
||||||
Get the workspace associated to the given id if the user has the permission
|
Get the workspace associated to the given id if the user has the permission
|
||||||
|
@ -77,7 +93,7 @@ class CoreService:
|
||||||
if specific:
|
if specific:
|
||||||
application_qs = self.handler.filter_specific_applications(
|
application_qs = self.handler.filter_specific_applications(
|
||||||
application_qs,
|
application_qs,
|
||||||
per_content_type_queryset_hook=self._filter_specific_queryset(
|
per_content_type_queryset_hook=self._enhance_and_filter_application_queryset(
|
||||||
user, workspace
|
user, workspace
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
@ -120,7 +136,7 @@ class CoreService:
|
||||||
if specific:
|
if specific:
|
||||||
application = specific_iterator(
|
application = specific_iterator(
|
||||||
[application],
|
[application],
|
||||||
per_content_type_queryset_hook=self._filter_specific_queryset(
|
per_content_type_queryset_hook=self._enhance_and_filter_application_queryset(
|
||||||
user, application.workspace
|
user, application.workspace
|
||||||
),
|
),
|
||||||
base_model=Application,
|
base_model=Application,
|
||||||
|
|
Loading…
Add table
Reference in a new issue