[GIT-76] fix: allow teamspace leads to edit and delete teamspace details (#5685)

This commit is contained in:
Anmol Singh Bhatia
2026-02-03 23:37:26 +05:30
committed by sriram veeraghanta
parent 61d6c596ee
commit 16ef6f688c
3 changed files with 51 additions and 13 deletions
+44 -9
View File
@@ -14,7 +14,7 @@ import random
import json
# Django imports
from django.db.models import OuterRef, Subquery, Exists
from django.db.models import OuterRef, Subquery, Exists, Q
from django.db.models.functions import Coalesce
from django.contrib.postgres.aggregates import ArrayAgg
from django.core.serializers.json import DjangoJSONEncoder
@@ -27,7 +27,7 @@ from rest_framework.response import Response
# Module imports
from plane.ee.views.base import BaseAPIView
from plane.ee.permissions import WorkspaceUserPermission
from plane.db.models import Workspace
from plane.db.models import Workspace, WorkspaceMember
from plane.ee.models import Teamspace, TeamspaceProject, TeamspaceMember
from plane.ee.serializers import TeamspaceSerializer
from plane.payment.flags.flag import FeatureFlag
@@ -41,6 +41,28 @@ class TeamspaceBaseEndpoint(BaseAPIView):
def team_space_id(self):
return self.kwargs.get("team_space_id")
def is_admin_or_teamspace_lead(self, request, slug, team_space_id):
"""Check if user is workspace admin, teamspace lead, or teamspace member"""
# Check if workspace admin
is_admin = WorkspaceMember.objects.filter(
member=request.user,
workspace__slug=slug,
is_active=True,
role=ROLE.ADMIN.value,
).exists()
if is_admin:
return True
# Check if teamspace lead
is_lead = Teamspace.objects.filter(
pk=team_space_id,
workspace__slug=slug,
lead=request.user,
).exists()
return is_lead
class TeamspaceEndpoint(TeamspaceBaseEndpoint):
permission_classes = [WorkspaceUserPermission]
@@ -68,9 +90,15 @@ class TeamspaceEndpoint(TeamspaceBaseEndpoint):
.annotate(
is_member=Exists(
TeamspaceMember.objects.filter(team_space=OuterRef("pk"), member_id=self.request.user.id)
)
),
is_workspace_admin=Exists(
WorkspaceMember.objects.filter(
workspace__slug=slug, member=self.request.user, role=ROLE.ADMIN.value, is_active=True
)
),
)
.get(workspace__slug=slug, pk=team_space_id, is_member=True)
.filter(Q(is_member=True) | Q(is_workspace_admin=True))
.get(workspace__slug=slug, pk=team_space_id)
)
def get_team_spaces(self, slug):
@@ -180,10 +208,17 @@ class TeamspaceEndpoint(TeamspaceBaseEndpoint):
except Exception as e:
return Response({"error": str(e)}, status=status.HTTP_400_BAD_REQUEST)
@allow_permission(level="WORKSPACE", allowed_roles=[ROLE.ADMIN])
@allow_permission(level="WORKSPACE", allowed_roles=[ROLE.ADMIN, ROLE.MEMBER])
@check_feature_flag(FeatureFlag.TEAMSPACES)
def patch(self, request, slug, team_space_id):
try:
# # Check if user is workspace admin or teamspace lead
if not self.is_admin_or_teamspace_lead(request, slug, team_space_id):
return Response(
{"error": "You don't have permission to edit this teamspace."},
status=status.HTTP_403_FORBIDDEN,
)
# Get team space by pk
team_space = self.get_team_space(slug, team_space_id)
# Get workspace
@@ -256,17 +291,17 @@ class TeamspaceEndpoint(TeamspaceBaseEndpoint):
except Teamspace.DoesNotExist:
return Response({"error": "Team space not found"}, status=status.HTTP_404_NOT_FOUND)
@allow_permission(level="WORKSPACE", allowed_roles=[ROLE.ADMIN])
@allow_permission(level="WORKSPACE", allowed_roles=[ROLE.ADMIN, ROLE.MEMBER])
@check_feature_flag(FeatureFlag.TEAMSPACES)
def delete(self, request, slug, team_space_id):
"""
Delete team space by pk
"""
try:
# The current deleting user should be part of the team space
if not TeamspaceMember.objects.filter(team_space_id=team_space_id, member_id=request.user).exists():
# # Check if user is workspace admin or teamspace lead
if not self.is_admin_or_teamspace_lead(request, slug, team_space_id):
return Response(
{"error": "You are not part of the team space"},
{"error": "You don't have permission to delete this teamspace."},
status=status.HTTP_403_FORBIDDEN,
)
@@ -42,7 +42,7 @@ type UpdateTeamspaceProjectsButtonProps = {
}) => ReactNode;
};
const TOOLTIP_CONTENT = "Contact teamspace admin";
const TOOLTIP_CONTENT = "You don't have permission to add project.";
export const UpdateTeamspaceProjectsButton = observer(function UpdateTeamspaceProjectsButton(
props: UpdateTeamspaceProjectsButtonProps
@@ -18,7 +18,7 @@ import { EUserPermissionsLevel } from "@plane/constants";
import { EUserWorkspaceRoles } from "@plane/types";
import { ContentWrapper, ERowVariant } from "@plane/ui";
// hooks
import { useUserPermissions } from "@/hooks/store/user";
import { useUser, useUserPermissions } from "@/hooks/store/user";
// plane web imports
import { useTeamspaces } from "@/plane-web/hooks/store";
// local imports
@@ -34,10 +34,13 @@ export const TeamsOverviewRoot = observer(function TeamsOverviewRoot(props: TTea
// router
const { workspaceSlug } = useParams();
// hooks
const { data: currentUser } = useUser();
const { allowPermissions } = useUserPermissions();
const { isCurrentUserMemberOfTeamspace } = useTeamspaces();
const { isCurrentUserMemberOfTeamspace, getTeamspaceById } = useTeamspaces();
// derived values
const teamspace = getTeamspaceById(teamspaceId);
const isTeamspaceMember = isCurrentUserMemberOfTeamspace(teamspaceId);
const isTeamspaceLead = currentUser?.id === teamspace?.lead_id;
const hasAdminLevelPermissions = allowPermissions(
[EUserWorkspaceRoles.ADMIN],
EUserPermissionsLevel.WORKSPACE,
@@ -49,7 +52,7 @@ export const TeamsOverviewRoot = observer(function TeamsOverviewRoot(props: TTea
<div className="flex w-full h-full">
<TeamsOverviewContent
teamspaceId={teamspaceId}
isEditingAllowed={hasAdminLevelPermissions && isTeamspaceMember}
isEditingAllowed={(hasAdminLevelPermissions || isTeamspaceLead) && isTeamspaceMember}
/>
{isTeamspaceMember && <TeamsOverviewSidebar teamspaceId={teamspaceId} />}
</div>