from apps.orgs.models import Organization, OrganizationMembership, OrganizationInvitation, Role, RoleMembership, TrainingFile from apps.orgs.serializers import ModelSerializer, OrganizationSerializer, OrganizationMembershipSerializer, OrganizationInvitationSerializer, RoleSerializer, RoleMembershipSerializer, TrainingFileSerializer from rest_framework.viewsets import ModelViewSet from rest_framework.permissions import IsAuthenticated from django.db.models import Q from rest_framework.response import Response from rest_framework.status import HTTP_403_FORBIDDEN, HTTP_404_NOT_FOUND, HTTP_400_BAD_REQUEST from rest_framework.decorators import action from django.utils import timezone from apps.users.models import User from apps.users.serializers import UserSerializer from rest_framework.parsers import MultiPartParser, FormParser class OrganizationViewSet(ModelViewSet): queryset = Organization.objects.all() serializer_class = OrganizationSerializer permission_classes = [IsAuthenticated] lookup_field = 'uuid' def get_queryset(self): return Organization.objects.filter(Q(memberships__user = self.request.user) | Q(owner = self.request.user)).distinct() def perform_create(self, serializer): organization = serializer.save(owner=self.request.user) OrganizationMembership.objects.create(user = self.request.user, organization = organization) def update(self, request, *args, **kwargs): if not request.user.is_manager: return Response({'error': 'Only managers can update organization details'}, status=HTTP_403_FORBIDDEN) return super().update(request, *args, **kwargs) @action(detail=True, methods=['post'], url_path='create-invite') def create_invite(self, request, uuid = None): organization = self.get_object() if not request.user.is_manager: return Response({'error': 'Only managers can create invites'}, status = HTTP_403_FORBIDDEN) max_uses = request.query_params.get('max_uses') max_uses = int(max_uses) if max_uses and max_uses.isdigit() and int(max_uses) > 0 else 1 invitation = OrganizationInvitation.objects.create( organization = organization, created_by = request.user, max_uses = max_uses ) return Response(OrganizationInvitationSerializer(invitation, context={'request': request}).data) @action(detail=False, methods=['post'], url_path='join/(?P[0-9a-f-]{36})') def join(self, request, token = None): try: invitation = OrganizationInvitation.objects.get(token = token) except OrganizationInvitation.DoesNotExist: return Response({'error': 'Invalid invitation token'}, status = HTTP_404_NOT_FOUND) if not invitation.is_active or invitation.expires_at < timezone.now(): return Response({'error': 'Invitation token is no longer valid'}, status = HTTP_400_BAD_REQUEST) if invitation.uses >= invitation.max_uses: invitation.is_active = False invitation.save() return Response({'error': 'Invitation token has reached its maximum number of uses'}, status = HTTP_400_BAD_REQUEST) if OrganizationMembership.objects.filter(user = request.user, organization = invitation.organization).exists(): return Response({'error': 'You are already a member of this organization'}, status = HTTP_403_FORBIDDEN) OrganizationMembership.objects.create(user = request.user, organization = invitation.organization) invitation.uses += 1 if invitation.uses >= invitation.max_uses: invitation.is_active = False invitation.save() organization_data = OrganizationSerializer(invitation.organization, context={'request': request}).data organization_data['message'] = 'Successfully joined the organization' organization_data['success'] = True return Response(organization_data) @action(detail=True, methods=['post'], url_path='leave') def leave(self, request, uuid = None): organization = self.get_object() try: membership = OrganizationMembership.objects.get(user = request.user, organization = organization) except OrganizationMembership.DoesNotExist: return Response({'error': 'You are not a member of this organization'}, status = HTTP_403_FORBIDDEN) if organization.owner == request.user: return Response({'error': 'The owner cannot leave the organization. Please transfer ownership or delete the organization.'}, status = HTTP_403_FORBIDDEN) membership.delete() return Response({'message': 'Successfully left the organization'}) @action(detail=True, methods=['get'], url_path='invite') def list_invites(self, request, uuid = None): if not request.user.is_manager: return Response({'error': 'Only managers can view invites'}, status = HTTP_403_FORBIDDEN) organization = self.get_object() invites = OrganizationInvitation.objects.filter(organization = organization, is_active = True) serializer = OrganizationInvitationSerializer(invites, many = True, context={'request': request}) return Response(serializer.data) @action(detail=True, methods=['get'], url_path='invite/(?P[0-9a-f-]{36})') def invite_detail(self, request, uuid = None, token = None): if not request.user.is_manager: return Response({'error': 'Only managers can view invite details'}, status = HTTP_403_FORBIDDEN) organization = self.get_object() try: invitation = OrganizationInvitation.objects.get(token = token, organization = organization) except OrganizationInvitation.DoesNotExist: return Response({'error': 'Invalid invitation token'}, status = HTTP_403_FORBIDDEN) serializer = OrganizationInvitationSerializer(invitation, context={'request': request}) return Response(serializer.data) @action(detail=True, methods=['post', 'delete'], url_path='invite/(?P[0-9a-f-]{36})/revoke') def revoke_invite(self, request, uuid = None, token = None): if not request.user.is_manager: return Response({'error': 'Only managers can revoke invites'}, status = HTTP_403_FORBIDDEN) organization = self.get_object() try: invitation = OrganizationInvitation.objects.get(token = token, organization = organization) except OrganizationInvitation.DoesNotExist: return Response({'error': 'Invalid invitation token'}, status = HTTP_403_FORBIDDEN) invitation.is_active = False invitation.save() return Response({'message': 'Invitation successfully revoked'}) @action(detail=True, methods=['get'], url_path='member') def list_members(self, request, uuid = None): if not request.user.is_manager: return Response({'error': 'Only managers can view members'}, status = HTTP_403_FORBIDDEN) organization = self.get_object() memberships = User.objects.filter(organization_memberships__organization = organization) serializer = UserSerializer(memberships, many = True) return Response(serializer.data) @action(detail=True, methods=['post'], url_path=r'member/(?P\d+)/remove') def remove_member(self, request, uuid = None, user_id = None): if not request.user.is_manager: return Response({'error': 'Only managers can remove members'}, status = HTTP_403_FORBIDDEN) organization = self.get_object() try: membership = OrganizationMembership.objects.get(user__id = user_id, organization = organization) except OrganizationMembership.DoesNotExist: return Response({'error': 'User is not a member of this organization'}, status = HTTP_403_FORBIDDEN) if membership.user == organization.owner: return Response({'error': 'Cannot remove the owner from the organization'}, status = HTTP_403_FORBIDDEN) membership.delete() return Response({'message': 'Member successfully removed from the organization'}) @action(detail=True, methods=['get', 'post'], url_path='role') def role(self, request, uuid = None): organization = self.get_object() if request.method == 'GET': roles = Role.objects.filter(organization = organization) serializer = RoleSerializer(roles, many = True) return Response(serializer.data) if not request.user.is_manager: return Response({'error': 'Only managers can create roles'}, status = HTTP_403_FORBIDDEN) name = request.data.get('name') if not name: return Response({'error': 'Role name is required'}, status = HTTP_403_FORBIDDEN) role = Role.objects.create(name = name, organization = organization) serializer = RoleSerializer(role) return Response(serializer.data) @action(detail=True, methods=['post'], url_path='role/(?P[0-9a-f-]{36})/delete') def delete_role(self, request, uuid = None, role_uuid = None): if not request.user.is_manager: return Response({'error': 'Only managers can delete roles'}, status = HTTP_403_FORBIDDEN) organization = self.get_object() try: role = Role.objects.get(uuid = role_uuid, organization = organization) except Role.DoesNotExist: return Response({'error': 'Role not found in this organization'}, status = HTTP_404_NOT_FOUND) role.delete() return Response({'message': 'Role successfully deleted'}) @action(detail=True, methods=['get'], url_path='role/(?P[0-9a-f-]{36})/member') def list_role_members(self, request, uuid = None, role_uuid = None): organization = self.get_object() try: role = Role.objects.get(uuid = role_uuid, organization = organization) except Role.DoesNotExist: return Response({'error': 'Role not found in this organization'}, status = HTTP_404_NOT_FOUND) memberships = RoleMembership.objects.filter(role = role) serializer = RoleMembershipSerializer(memberships, many = True) return Response(serializer.data) @action(detail=True, methods=['get', 'post'], url_path='training-file') def training_files(self, request, uuid = None): organization = self.get_object() if request.method == 'GET': training_files = TrainingFile.objects.filter(organization=organization) serializer = TrainingFileSerializer(training_files, many=True) return Response(serializer.data) if not (organization.owner == request.user or organization.memberships.filter(user=request.user).exists()): return Response( {'error': 'You do not have permission to upload files to this organization'}, status=HTTP_403_FORBIDDEN ) serializer = TrainingFileSerializer(data=request.data) if serializer.is_valid(): serializer.save(uploaded_by=request.user, organization=organization) return Response(serializer.data, status=201) return Response(serializer.errors, status=HTTP_400_BAD_REQUEST) @action(detail=True, methods=['get', 'delete'], url_path='training-file/(?P[0-9a-f-]{36})') def training_file_detail(self, request, uuid = None, file_uuid = None): organization = self.get_object() try: training_file = TrainingFile.objects.get(uuid=file_uuid, organization=organization) except TrainingFile.DoesNotExist: return Response({'error': 'Training file not found'}, status=HTTP_404_NOT_FOUND) if request.method == 'GET': serializer = TrainingFileSerializer(training_file) return Response(serializer.data) if not (training_file.uploaded_by == request.user or training_file.organization.owner == request.user or request.user.is_manager): return Response( {'error': 'You do not have permission to delete this file'}, status=HTTP_403_FORBIDDEN ) file_name = training_file.file_name training_file.delete() return Response({'message': f'File "{file_name}" successfully deleted'})