mirror of
https://gitlab.com/bramw/baserow.git
synced 2025-04-07 06:15:36 +00:00
Merge branch 'reduce-nr-queries-login' into 'develop'
adding ability to enhance workspace queryset See merge request baserow/baserow!3321
This commit is contained in:
commit
e9f4588060
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.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
|
||||
)
|
||||
|
|
|
@ -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."
|
||||
|
|
|
@ -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 []
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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",
|
||||
)
|
||||
|
|
|
@ -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
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -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."
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
Loading…
Add table
Reference in a new issue