1
0
Fork 0
mirror of https://gitlab.com/bramw/baserow.git synced 2025-04-07 06:15:36 +00:00

adding ability to enhance workspace queryset

This commit is contained in:
Davide Silvestri 2025-04-01 18:07:14 +02:00
parent 8a679d0550
commit 09c22ae0af
9 changed files with 142 additions and 64 deletions
backend/src/baserow

View file

@ -43,7 +43,7 @@ from baserow.core.job_types import DuplicateApplicationJobType
from baserow.core.jobs.exceptions import MaxJobCountExceeded
from baserow.core.jobs.handler import JobHandler
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.service import CoreService
from baserow.core.trash.exceptions import CannotDeleteAlreadyDeletedItem
@ -81,13 +81,15 @@ class AllApplicationsView(APIView):
returned.
"""
workspaces = Workspace.objects.filter(users=request.user).prefetch_related(
"workspaceuser_set", "template_set"
)
workspaces = CoreService().list_workspaces(request.user)
applications_qs = Application.objects.none()
for workspace in workspaces:
workspace_applications_qs = CoreService().list_applications_in_workspace(
request.user, workspace
)
applications_qs = applications_qs.union(
CoreService().list_applications_in_workspace(request.user, workspace)
workspace_applications_qs.order_by(), all=True
)
data = [
@ -148,7 +150,6 @@ class ApplicationsView(APIView):
"""
workspace = CoreService().get_workspace(request.user, workspace_id)
applications = CoreService().list_applications_in_workspace(
request.user, workspace
)

View file

@ -339,6 +339,37 @@ class CollaboratorSerializer(serializers.Serializer):
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):
duplicate_data = serializers.BooleanField(
default=False, help_text="Indicates whether the data should be duplicated."

View file

@ -39,21 +39,3 @@ class BaserowFormulaSelectOptionsSerializer(serializers.ListField):
return [self.child.to_representation(item) for item in select_options]
else:
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 []

View file

@ -70,6 +70,7 @@ from baserow.contrib.database.api.fields.errors import (
ERROR_WITH_FORMULA,
)
from baserow.contrib.database.api.fields.serializers import (
AvailableCollaboratorsSerializer,
BaserowBooleanField,
CollaboratorSerializer,
DurationFieldSerializer,
@ -1506,15 +1507,16 @@ class LastModifiedByFieldType(ReadOnlyFieldType):
source_field_name = "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_overrides = {
"available_collaborators": serializers.ListField(
child=CollaboratorSerializer(),
read_only=True,
source="table.database.workspace.users.all",
),
"available_collaborators": AvailableCollaboratorsSerializer(),
}
def can_represent_collaborators(self, field):
return True
def get_model_field(self, instance, **kwargs):
kwargs["null"] = True
kwargs["blank"] = True
@ -1731,15 +1733,16 @@ class CreatedByFieldType(ReadOnlyFieldType):
source_field_name = "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_overrides = {
"available_collaborators": serializers.ListField(
child=CollaboratorSerializer(),
read_only=True,
source="table.database.workspace.users.all",
),
"available_collaborators": AvailableCollaboratorsSerializer(),
}
def can_represent_collaborators(self, field):
return True
def get_model_field(self, instance, **kwargs):
kwargs["null"] = True
kwargs["blank"] = True
@ -6108,18 +6111,24 @@ class MultipleCollaboratorsFieldType(
model_class = MultipleCollaboratorsField
can_get_unique_values = False
allowed_fields = ["notify_user_when_added"]
serializer_field_names = ["available_collaborators", "notify_user_when_added"]
serializer_field_overrides = {
"available_collaborators": serializers.ListField(
child=CollaboratorSerializer(),
read_only=True,
source="table.database.workspace.users.all",
),
request_serializer_field_names = ["notify_user_when_added"]
request_serializer_field_overrides = {
"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
_can_group_by = True
def can_represent_collaborators(self, field):
return True
def get_serializer_field(self, instance, **kwargs):
required = kwargs.pop("required", False)
field_serializer = CollaboratorSerializer(

View file

@ -208,6 +208,7 @@ class FieldHandler(metaclass=baserow_trace_methods(tracer)):
queryset=User.objects.filter(profile__to_be_deleted=False).order_by(
"first_name"
),
to_attr="available_collaborators",
),
"select_options",
)

View file

@ -1345,11 +1345,10 @@ class BaserowFormulaArrayType(
@classmethod
def get_serializer_field_overrides(cls):
from baserow.contrib.database.api.fields.serializers import (
CollaboratorSerializer,
AvailableCollaboratorsSerializer,
SelectOptionSerializer,
)
from baserow.contrib.database.api.formula.serializers import (
BaserowFormulaCollaboratorsSerializer,
BaserowFormulaSelectOptionsSerializer,
)
@ -1360,11 +1359,8 @@ class BaserowFormulaArrayType(
allow_null=True,
read_only=True,
),
"available_collaborators": BaserowFormulaCollaboratorsSerializer(
child=CollaboratorSerializer(),
required=False,
allow_null=True,
read_only=True,
"available_collaborators": AvailableCollaboratorsSerializer(
required=False, allow_null=True
),
}
@ -1779,18 +1775,12 @@ class BaserowFormulaMultipleCollaboratorsType(BaserowJSONBObjectBaseType):
@classmethod
def get_serializer_field_overrides(cls):
from baserow.contrib.database.api.fields.serializers import (
CollaboratorSerializer,
)
from baserow.contrib.database.api.formula.serializers import (
BaserowFormulaCollaboratorsSerializer,
AvailableCollaboratorsSerializer,
)
return {
"available_collaborators": BaserowFormulaCollaboratorsSerializer(
child=CollaboratorSerializer(),
allow_null=True,
required=False,
read_only=True,
"available_collaborators": AvailableCollaboratorsSerializer(
allow_null=True, required=False
)
}

View file

@ -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(
self, workspace_id: int, base_queryset: QuerySet = None
) -> Workspace:
@ -535,11 +570,10 @@ class CoreHandler(metaclass=baserow_trace_methods(tracer)):
:return: The requested workspace instance of the provided id.
"""
if base_queryset is None:
base_queryset = Workspace.objects
workspace_qs = self.get_enhanced_workspace_queryset(base_queryset)
try:
workspace = base_queryset.get(id=workspace_id)
workspace = workspace_qs.get(id=workspace_id)
except Workspace.DoesNotExist:
raise WorkspaceDoesNotExist(
f"The workspace with id {workspace_id} does not exist."

View file

@ -215,8 +215,22 @@ class Plugin(APIUrlsInstanceMixin, Instance):
: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
abstraction made specifically for Baserow. It allows a plugin developer to

View file

@ -8,6 +8,7 @@ from baserow.core.handler import CoreHandler
from baserow.core.models import Application, Workspace
from baserow.core.operations import (
ListApplicationsWorkspaceOperationType,
ListWorkspacesOperationType,
ReadApplicationOperationType,
ReadWorkspaceOperationType,
)
@ -18,11 +19,26 @@ class CoreService:
def __init__(self):
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(
model
).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:
"""
Get the workspace associated to the given id if the user has the permission
@ -77,7 +93,7 @@ class CoreService:
if specific:
application_qs = self.handler.filter_specific_applications(
application_qs,
per_content_type_queryset_hook=self._filter_specific_queryset(
per_content_type_queryset_hook=self._enhance_and_filter_application_queryset(
user, workspace
),
)
@ -120,7 +136,7 @@ class CoreService:
if specific:
application = specific_iterator(
[application],
per_content_type_queryset_hook=self._filter_specific_queryset(
per_content_type_queryset_hook=self._enhance_and_filter_application_queryset(
user, application.workspace
),
base_model=Application,