♻️(backend) simplify roles by returning only the max role
We were returning the list of roles a user has on a document (direct and inherited). Now that we introduced priority on roles, we are able to determine what is the max role and return only this one. This commit also changes the role that is returned for the restricted reach: we now return None because the role is not relevant in this case.
This commit is contained in:
committed by
Anthony LC
parent
0a9a583a67
commit
611ba496d2
@@ -171,7 +171,7 @@ class ListDocumentSerializer(serializers.ModelSerializer):
|
||||
is_favorite = serializers.BooleanField(read_only=True)
|
||||
nb_accesses_ancestors = serializers.IntegerField(read_only=True)
|
||||
nb_accesses_direct = serializers.IntegerField(read_only=True)
|
||||
user_roles = serializers.SerializerMethodField(read_only=True)
|
||||
user_role = serializers.SerializerMethodField(read_only=True)
|
||||
abilities = serializers.SerializerMethodField(read_only=True)
|
||||
|
||||
class Meta:
|
||||
@@ -192,7 +192,7 @@ class ListDocumentSerializer(serializers.ModelSerializer):
|
||||
"path",
|
||||
"title",
|
||||
"updated_at",
|
||||
"user_roles",
|
||||
"user_role",
|
||||
]
|
||||
read_only_fields = [
|
||||
"id",
|
||||
@@ -209,34 +209,36 @@ class ListDocumentSerializer(serializers.ModelSerializer):
|
||||
"numchild",
|
||||
"path",
|
||||
"updated_at",
|
||||
"user_roles",
|
||||
"user_role",
|
||||
]
|
||||
|
||||
def get_abilities(self, document) -> dict:
|
||||
def to_representation(self, instance):
|
||||
"""Precompute once per instance"""
|
||||
paths_links_mapping = self.context.get("paths_links_mapping")
|
||||
|
||||
if paths_links_mapping is not None:
|
||||
links = paths_links_mapping.get(instance.path[: -instance.steplen], [])
|
||||
instance.ancestors_link_definition = choices.get_equivalent_link_definition(
|
||||
links
|
||||
)
|
||||
|
||||
return super().to_representation(instance)
|
||||
|
||||
def get_abilities(self, instance) -> dict:
|
||||
"""Return abilities of the logged-in user on the instance."""
|
||||
request = self.context.get("request")
|
||||
if not request:
|
||||
return {}
|
||||
|
||||
if request:
|
||||
paths_links_mapping = self.context.get("paths_links_mapping", None)
|
||||
# Retrieve ancestor links from paths_links_mapping (if provided)
|
||||
ancestors_links = (
|
||||
paths_links_mapping.get(document.path[: -document.steplen])
|
||||
if paths_links_mapping
|
||||
else None
|
||||
)
|
||||
return document.get_abilities(request.user, ancestors_links=ancestors_links)
|
||||
return instance.get_abilities(request.user)
|
||||
|
||||
return {}
|
||||
|
||||
def get_user_roles(self, document):
|
||||
def get_user_role(self, instance):
|
||||
"""
|
||||
Return roles of the logged-in user for the current document,
|
||||
taking into account ancestors.
|
||||
"""
|
||||
request = self.context.get("request")
|
||||
if request:
|
||||
return document.get_roles(request.user)
|
||||
return []
|
||||
return instance.get_role(request.user) if request else None
|
||||
|
||||
|
||||
class DocumentSerializer(ListDocumentSerializer):
|
||||
@@ -264,7 +266,7 @@ class DocumentSerializer(ListDocumentSerializer):
|
||||
"path",
|
||||
"title",
|
||||
"updated_at",
|
||||
"user_roles",
|
||||
"user_role",
|
||||
"websocket",
|
||||
]
|
||||
read_only_fields = [
|
||||
@@ -281,7 +283,7 @@ class DocumentSerializer(ListDocumentSerializer):
|
||||
"numchild",
|
||||
"path",
|
||||
"updated_at",
|
||||
"user_roles",
|
||||
"user_role",
|
||||
]
|
||||
|
||||
def get_fields(self):
|
||||
|
||||
@@ -443,14 +443,15 @@ class DocumentViewSet(
|
||||
queryset = queryset.annotate_user_roles(user)
|
||||
return queryset
|
||||
|
||||
def get_response_for_queryset(self, queryset):
|
||||
def get_response_for_queryset(self, queryset, context=None):
|
||||
"""Return paginated response for the queryset if requested."""
|
||||
context = context or self.get_serializer_context()
|
||||
page = self.paginate_queryset(queryset)
|
||||
if page is not None:
|
||||
serializer = self.get_serializer(page, many=True)
|
||||
serializer = self.get_serializer(page, many=True, context=context)
|
||||
return self.get_paginated_response(serializer.data)
|
||||
|
||||
serializer = self.get_serializer(queryset, many=True)
|
||||
serializer = self.get_serializer(queryset, many=True, context=context)
|
||||
return drf.response.Response(serializer.data)
|
||||
|
||||
def list(self, request, *args, **kwargs):
|
||||
@@ -460,9 +461,6 @@ class DocumentViewSet(
|
||||
This method applies filtering based on request parameters using `ListDocumentFilter`.
|
||||
It performs early filtering on model fields, annotates user roles, and removes
|
||||
descendant documents to keep only the highest ancestors readable by the current user.
|
||||
|
||||
Additional annotations (e.g., `is_highest_ancestor_for_user`, favorite status) are
|
||||
applied before ordering and returning the response.
|
||||
"""
|
||||
user = self.request.user
|
||||
|
||||
@@ -490,12 +488,6 @@ class DocumentViewSet(
|
||||
)
|
||||
queryset = queryset.filter(path__in=root_paths)
|
||||
|
||||
# Annotate the queryset with an attribute marking instances as highest ancestor
|
||||
# in order to save some time while computing abilities on the instance
|
||||
queryset = queryset.annotate(
|
||||
is_highest_ancestor_for_user=db.Value(True, output_field=db.BooleanField())
|
||||
)
|
||||
|
||||
# Annotate favorite status and filter if applicable as late as possible
|
||||
queryset = queryset.annotate_is_favorite(user)
|
||||
queryset = filterset.filters["is_favorite"].filter(
|
||||
@@ -827,7 +819,17 @@ class DocumentViewSet(
|
||||
|
||||
queryset = filterset.qs
|
||||
|
||||
return self.get_response_for_queryset(queryset)
|
||||
# Pass ancestors' links paths mapping to the serializer as a context variable
|
||||
# in order to allow saving time while computing abilities on the instance
|
||||
paths_links_mapping = document.compute_ancestors_links_paths_mapping()
|
||||
|
||||
return self.get_response_for_queryset(
|
||||
queryset,
|
||||
context={
|
||||
"request": request,
|
||||
"paths_links_mapping": paths_links_mapping,
|
||||
},
|
||||
)
|
||||
|
||||
@drf.decorators.action(
|
||||
detail=True,
|
||||
@@ -886,13 +888,6 @@ class DocumentViewSet(
|
||||
ancestors_links = []
|
||||
children_clause = db.Q()
|
||||
for ancestor in ancestors:
|
||||
if ancestor.depth < highest_readable.depth:
|
||||
continue
|
||||
|
||||
children_clause |= db.Q(
|
||||
path__startswith=ancestor.path, depth=ancestor.depth + 1
|
||||
)
|
||||
|
||||
# Compute cache for ancestors links to avoid many queries while computing
|
||||
# abilities for his documents in the tree!
|
||||
ancestors_links.append(
|
||||
@@ -900,25 +895,21 @@ class DocumentViewSet(
|
||||
)
|
||||
paths_links_mapping[ancestor.path] = ancestors_links.copy()
|
||||
|
||||
if ancestor.depth < highest_readable.depth:
|
||||
continue
|
||||
|
||||
children_clause |= db.Q(
|
||||
path__startswith=ancestor.path, depth=ancestor.depth + 1
|
||||
)
|
||||
|
||||
children = self.queryset.filter(children_clause, deleted_at__isnull=True)
|
||||
|
||||
queryset = ancestors.filter(depth__gte=highest_readable.depth) | children
|
||||
queryset = queryset.order_by("path")
|
||||
# Annotate if the current document is the highest ancestor for the user
|
||||
queryset = queryset.annotate(
|
||||
is_highest_ancestor_for_user=db.Case(
|
||||
db.When(
|
||||
path=db.Value(highest_readable.path),
|
||||
then=db.Value(True),
|
||||
),
|
||||
default=db.Value(False),
|
||||
output_field=db.BooleanField(),
|
||||
)
|
||||
)
|
||||
queryset = queryset.annotate_user_roles(user)
|
||||
queryset = queryset.annotate_is_favorite(user)
|
||||
|
||||
# Pass ancestors' links definitions to the serializer as a context variable
|
||||
# Pass ancestors' links paths mapping to the serializer as a context variable
|
||||
# in order to allow saving time while computing abilities on the instance
|
||||
serializer = self.get_serializer(
|
||||
queryset,
|
||||
@@ -1520,8 +1511,8 @@ class DocumentAccessViewSet(
|
||||
except models.Document.DoesNotExist:
|
||||
return drf.response.Response([])
|
||||
|
||||
roles = set(document.get_roles(user))
|
||||
if not roles:
|
||||
role = document.get_role(user)
|
||||
if role is None:
|
||||
return drf.response.Response([])
|
||||
|
||||
ancestors = (
|
||||
@@ -1539,7 +1530,7 @@ class DocumentAccessViewSet(
|
||||
document__in=ancestors.filter(depth__gte=highest_readable.depth)
|
||||
)
|
||||
|
||||
is_privileged = bool(roles.intersection(set(choices.PRIVILEGED_ROLES)))
|
||||
is_privileged = role in choices.PRIVILEGED_ROLES
|
||||
if is_privileged:
|
||||
serializer_class = serializers.DocumentAccessSerializer
|
||||
else:
|
||||
|
||||
Reference in New Issue
Block a user