feat(workspaces): expose role-aware membership details
This commit is contained in:
@@ -4,6 +4,7 @@ from rest_framework import serializers
|
||||
|
||||
from apps.notifications.services import notify_workspace_membership_added
|
||||
from apps.users.models import User
|
||||
from apps.workspaces.services import WORKSPACE_MEMBERS_VIEW, has_workspace_capability
|
||||
from core.serializers.base import BaseModelSerializer
|
||||
from apps.workspaces.models import PriceUnit, Workspace, WorkspaceMembership, WorkspaceUserRate
|
||||
from core.serializers.mini import UserMiniSerializer
|
||||
@@ -76,22 +77,36 @@ class WorkspaceSerializer(BaseModelSerializer):
|
||||
|
||||
|
||||
class WorkspaceMembershipSerializer(BaseModelSerializer):
|
||||
class Meta:
|
||||
model = WorkspaceMembership
|
||||
fields = BaseModelSerializer.Meta.fields + (
|
||||
"workspace",
|
||||
"user",
|
||||
user = serializers.SerializerMethodField()
|
||||
|
||||
class Meta:
|
||||
model = WorkspaceMembership
|
||||
fields = BaseModelSerializer.Meta.fields + (
|
||||
"workspace",
|
||||
"user",
|
||||
"role",
|
||||
"is_active",
|
||||
)
|
||||
|
||||
def to_representation(self, instance):
|
||||
data = super().to_representation(instance)
|
||||
data["user"] = UserMiniSerializer(
|
||||
instance.user,
|
||||
context=self.context
|
||||
).data
|
||||
return data
|
||||
def get_user(self, instance):
|
||||
request = self.context.get("request")
|
||||
viewer = getattr(request, "user", None)
|
||||
can_view_sensitive_details = bool(
|
||||
viewer
|
||||
and viewer.is_authenticated
|
||||
and has_workspace_capability(viewer, instance.workspace, WORKSPACE_MEMBERS_VIEW)
|
||||
)
|
||||
|
||||
user_data = UserMiniSerializer(instance.user, context=self.context).data
|
||||
if can_view_sensitive_details:
|
||||
return user_data
|
||||
|
||||
return {
|
||||
"id": user_data["id"],
|
||||
"first_name": user_data.get("first_name"),
|
||||
"last_name": user_data.get("last_name"),
|
||||
"profile_picture": user_data.get("profile_picture"),
|
||||
}
|
||||
|
||||
|
||||
class PriceUnitSerializer(BaseModelSerializer):
|
||||
|
||||
@@ -33,6 +33,7 @@ from apps.workspaces.models import PriceUnit, Workspace, WorkspaceMembership, Wo
|
||||
from apps.workspaces.services import (
|
||||
WORKSPACE_MEMBERS_VIEW,
|
||||
WORKSPACE_EDIT,
|
||||
WORKSPACE_VIEW,
|
||||
can_assign_workspace_role,
|
||||
can_change_workspace_membership,
|
||||
has_workspace_capability,
|
||||
@@ -102,7 +103,9 @@ class WorkspaceMembershipViewSet(ModelViewSet):
|
||||
).distinct()
|
||||
|
||||
def get_permissions(self):
|
||||
if self.action in ["list", "retrieve", "create", "update", "partial_update"]:
|
||||
if self.action in ["list", "retrieve"]:
|
||||
return [IsAuthenticated()]
|
||||
if self.action in ["create", "update", "partial_update"]:
|
||||
return [IsAuthenticated(), CanWorkspaceManageMembers()]
|
||||
if self.action in ["destroy"]:
|
||||
return [IsAuthenticated(), CanWorkspaceManageMembers()]
|
||||
@@ -118,7 +121,7 @@ class WorkspaceMembershipViewSet(ModelViewSet):
|
||||
)
|
||||
|
||||
workspace = get_object_or_404(Workspace, id=workspace_id, is_deleted=False)
|
||||
if not has_workspace_capability(request.user, workspace, WORKSPACE_MEMBERS_VIEW):
|
||||
if not has_workspace_capability(request.user, workspace, WORKSPACE_VIEW):
|
||||
return Response(
|
||||
{"detail": "You do not have permission to view workspace members."},
|
||||
status=status.HTTP_403_FORBIDDEN,
|
||||
|
||||
@@ -230,6 +230,31 @@ def test_member_project_manager_cannot_edit_project(api_client, member, project)
|
||||
assert response.status_code == 403
|
||||
|
||||
|
||||
def test_member_can_list_workspace_members_with_restricted_user_fields(api_client, member, workspace):
|
||||
api_client.force_authenticate(user=member)
|
||||
|
||||
response = api_client.get(f"/api/workspace-memberships/?workspace={workspace.id}")
|
||||
|
||||
assert response.status_code == 200
|
||||
payload = response.data.get("items", response.data) if isinstance(response.data, dict) else response.data
|
||||
assert len(payload) >= 1
|
||||
first_user = payload[0]["user"]
|
||||
assert "mobile" not in first_user
|
||||
assert "email" not in first_user
|
||||
|
||||
|
||||
def test_owner_can_list_workspace_members_with_full_user_fields(api_client, owner, workspace):
|
||||
api_client.force_authenticate(user=owner)
|
||||
|
||||
response = api_client.get(f"/api/workspace-memberships/?workspace={workspace.id}")
|
||||
|
||||
assert response.status_code == 200
|
||||
payload = response.data.get("items", response.data) if isinstance(response.data, dict) else response.data
|
||||
assert len(payload) >= 1
|
||||
first_user = payload[0]["user"]
|
||||
assert "mobile" in first_user
|
||||
|
||||
|
||||
def test_admin_cannot_change_owner_membership_but_canonical_owner_can(
|
||||
api_client, owner, admin, extra_owner, workspace
|
||||
):
|
||||
|
||||
Reference in New Issue
Block a user