from django.db.models import Q from django.shortcuts import get_object_or_404 from rest_framework import status from rest_framework.response import Response from rest_framework.filters import OrderingFilter, SearchFilter from django_filters.rest_framework import DjangoFilterBackend from rest_framework.viewsets import ModelViewSet from rest_framework.permissions import IsAuthenticated from apps.notifications.services import ( notify_workspace_membership_added, notify_workspace_membership_deactivated, notify_workspace_membership_removed, notify_workspace_membership_role_changed, ) from apps.workspaces.api.permissions import ( CanWorkspaceManageMembers, IsWorkspaceAdmin, IsWorkspaceMember, IsWorkspaceOwner, ) from apps.workspaces.api.serializers import WorkspaceMembershipSerializer, WorkspaceSerializer from apps.workspaces.api.filters import WorkspaceFilter, WorkspaceMembershipFilter from apps.workspaces.models import Workspace, WorkspaceMembership from apps.workspaces.services import ( WORKSPACE_MEMBERS_VIEW, can_assign_workspace_role, can_change_workspace_membership, has_workspace_capability, ) from core.paginations.limit_offset import CustomLimitOffsetPagination class WorkspaceViewSet(ModelViewSet): serializer_class = WorkspaceSerializer pagination_class = CustomLimitOffsetPagination filter_backends = (DjangoFilterBackend, OrderingFilter, SearchFilter) filterset_class = WorkspaceFilter search_fields = ("name", "description") ordering_fields = ("created_at", "updated_at", "name") ordering = ("-updated_at", "-created_at") def get_queryset(self): user = self.request.user if not user.is_authenticated: return Workspace.objects.none() return Workspace.objects.filter( Q(owner=user) | Q(memberships__user=user, memberships__is_active=True) ).distinct() def get_permissions(self): if self.action in ["list", "retrieve"]: return [IsAuthenticated(), IsWorkspaceMember()] if self.action in ["update", "partial_update"]: return [IsAuthenticated(), IsWorkspaceAdmin()] elif self.action == "destroy": return [IsAuthenticated(), IsWorkspaceOwner()] return [IsAuthenticated()] def perform_create(self, serializer): serializer.save(owner=self.request.user) class WorkspaceMembershipViewSet(ModelViewSet): serializer_class = WorkspaceMembershipSerializer pagination_class = CustomLimitOffsetPagination filter_backends = (DjangoFilterBackend, OrderingFilter, SearchFilter) filterset_class = WorkspaceMembershipFilter search_fields = ( "user__mobile", "user__email", "user__first_name", "user__last_name", "workspace__name" ) ordering_fields = ("joined_at", "created_at", "role") ordering = ("-created_at",) def get_queryset(self): user = self.request.user if not user.is_authenticated: return WorkspaceMembership.objects.none() return WorkspaceMembership.objects.filter( Q(workspace__owner=user) | Q(workspace__memberships__user=user, workspace__memberships__is_active=True) ).distinct() def get_permissions(self): if self.action in ["list", "retrieve", "create", "update", "partial_update"]: return [IsAuthenticated(), CanWorkspaceManageMembers()] if self.action in ["destroy"]: return [IsAuthenticated(), CanWorkspaceManageMembers()] return [IsAuthenticated()] def list(self, request, *args, **kwargs): workspace_id = request.query_params.get("workspace") if not workspace_id: return Response( {"detail": "workspace query parameter is required."}, status=status.HTTP_400_BAD_REQUEST, ) workspace = get_object_or_404(Workspace, id=workspace_id, is_deleted=False) if not has_workspace_capability(request.user, workspace, WORKSPACE_MEMBERS_VIEW): return Response( {"detail": "You do not have permission to view workspace members."}, status=status.HTTP_403_FORBIDDEN, ) return super().list(request, *args, **kwargs) def create(self, request, *args, **kwargs): """ Overridden to check permissions manually. Because the membership object doesn't exist yet, standard DRF object-level permissions won't catch payload-level workspace violations. """ workspace_id = request.data.get("workspace") if not workspace_id: return Response( {"workspace": ["This field is required."]}, status=status.HTTP_400_BAD_REQUEST ) workspace = get_object_or_404(Workspace, id=workspace_id) permission = IsWorkspaceAdmin() if not permission.has_object_permission(request, self, workspace): return Response( {"detail": "You must be a Workspace Admin or Owner to add members."}, status=status.HTTP_403_FORBIDDEN ) requested_role = request.data.get("role") if requested_role and not can_assign_workspace_role( request.user, workspace, requested_role, ): return Response( {"detail": "You do not have permission to assign this workspace role."}, status=status.HTTP_403_FORBIDDEN, ) serializer = self.get_serializer(data=request.data) serializer.is_valid(raise_exception=True) membership = serializer.save() notify_workspace_membership_added( actor=request.user, recipient=membership.user, workspace=membership.workspace, role=membership.role, ) return Response( WorkspaceMembershipSerializer(membership, context=self.get_serializer_context()).data, status=status.HTTP_201_CREATED, ) def update(self, request, *args, **kwargs): partial = kwargs.pop("partial", False) membership = self.get_object() previous_role = membership.role previous_is_active = membership.is_active requested_role = request.data.get("role") if not can_change_workspace_membership( request.user, membership, new_role=requested_role, ): return Response( {"detail": "You do not have permission to change this workspace membership."}, status=status.HTTP_403_FORBIDDEN, ) serializer = self.get_serializer(membership, data=request.data, partial=partial) serializer.is_valid(raise_exception=True) updated_membership = serializer.save() if not previous_is_active and updated_membership.is_active: notify_workspace_membership_added( actor=request.user, recipient=updated_membership.user, workspace=updated_membership.workspace, role=updated_membership.role, ) elif previous_is_active and not updated_membership.is_active: notify_workspace_membership_deactivated( actor=request.user, recipient=updated_membership.user, workspace=updated_membership.workspace, role=previous_role, ) elif previous_role != updated_membership.role: notify_workspace_membership_role_changed( actor=request.user, recipient=updated_membership.user, workspace=updated_membership.workspace, previous_role=previous_role, new_role=updated_membership.role, ) return Response( WorkspaceMembershipSerializer( updated_membership, context=self.get_serializer_context(), ).data, status=status.HTTP_200_OK, ) def partial_update(self, request, *args, **kwargs): kwargs["partial"] = True return self.update(request, *args, **kwargs) def destroy(self, request, *args, **kwargs): membership = self.get_object() if not can_change_workspace_membership(request.user, membership): return Response( {"detail": "You do not have permission to remove this workspace membership."}, status=status.HTTP_403_FORBIDDEN, ) recipient = membership.user workspace = membership.workspace role = membership.role membership.delete() notify_workspace_membership_removed( actor=request.user, recipient=recipient, workspace=workspace, role=role, ) return Response(status=status.HTTP_204_NO_CONTENT)