Files
qlockify-backend-deployment/apps/workspaces/api/views.py

239 lines
8.9 KiB
Python

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)