from django.db.models import Q from django.shortcuts import get_object_or_404 from drf_spectacular.utils import extend_schema, inline_serializer from rest_framework import serializers, status from rest_framework.pagination import LimitOffsetPagination from rest_framework.permissions import IsAuthenticated from rest_framework.response import Response from rest_framework.utils import json from rest_framework.views import APIView from common import swagger_params from common.models import Document, Profile, Teams from common.permissions import HasOrgContext from common.serializer import ( DocumentCreateSerializer, DocumentCreateSwaggerSerializer, DocumentEditSwaggerSerializer, DocumentSerializer, ProfileSerializer, ) class DocumentListView(APIView, LimitOffsetPagination): permission_classes = (IsAuthenticated, HasOrgContext) model = Document def get_context_data(self, **kwargs): params = self.request.query_params queryset = self.model.objects.filter(org=self.request.profile.org).order_by( "-id" ) if self.request.user.is_superuser or self.request.profile.role == "ADMIN": queryset = queryset else: if self.request.profile.documents(): doc_ids = self.request.profile.documents().values_list("id", flat=True) shared_ids = queryset.filter( Q(status="active") & Q(shared_to__id__in=[self.request.profile.id]) ).values_list("id", flat=True) queryset = queryset.filter(Q(id__in=doc_ids) | Q(id__in=shared_ids)) else: queryset = queryset.filter( Q(status="active") & Q(shared_to__id__in=[self.request.profile.id]) ) request_post = params if request_post: if request_post.get("title"): queryset = queryset.filter(title__icontains=request_post.get("title")) if request_post.get("status"): queryset = queryset.filter(status=request_post.get("status")) if request_post.get("shared_to"): queryset = queryset.filter( shared_to__id__in=json.loads(request_post.get("shared_to")) ) context = {} profile_list = Profile.objects.filter( is_active=True, org=self.request.profile.org ) if self.request.profile.role == "ADMIN" or self.request.profile.is_admin: profiles = profile_list.order_by("user__email") else: profiles = profile_list.filter(role="ADMIN").order_by("user__email") search = False if ( params.get("document_file") or params.get("status") or params.get("shared_to") ): search = True context["search"] = search queryset_documents_active = queryset.filter(status="active") results_documents_active = self.paginate_queryset( queryset_documents_active.distinct(), self.request, view=self ) documents_active = DocumentSerializer(results_documents_active, many=True).data if results_documents_active: offset = queryset_documents_active.filter( id__gte=results_documents_active[-1].id ).count() if offset == queryset_documents_active.count(): offset = None else: offset = 0 context["documents_active"] = { "documents_active_count": self.count, "documents_active": documents_active, "offset": offset, } queryset_documents_inactive = queryset.filter(status="inactive") results_documents_inactive = self.paginate_queryset( queryset_documents_inactive.distinct(), self.request, view=self ) documents_inactive = DocumentSerializer( results_documents_inactive, many=True ).data if results_documents_inactive: offset = queryset_documents_inactive.filter( id__gte=results_documents_active[-1].id ).count() if offset == queryset_documents_inactive.count(): offset = None else: offset = 0 context["documents_inactive"] = { "documents_inactive_count": self.count, "documents_inactive": documents_inactive, "offset": offset, } context["users"] = ProfileSerializer(profiles, many=True).data context["status_choices"] = Document.DOCUMENT_STATUS_CHOICE return context @extend_schema( tags=["documents"], operation_id="documents_list", parameters=swagger_params.document_get_params, responses={ 200: inline_serializer( name="DocumentListResponse", fields={ "search": serializers.BooleanField(), "documents_active": serializers.DictField(), "documents_inactive": serializers.DictField(), "users": ProfileSerializer(many=True), "status_choices": serializers.ListField(), }, ) }, ) def get(self, request, *args, **kwargs): context = self.get_context_data(**kwargs) return Response(context) @extend_schema( tags=["documents"], operation_id="documents_create", parameters=swagger_params.organization_params, request=DocumentCreateSwaggerSerializer, responses={ 201: inline_serializer( name="DocumentCreateResponse", fields={ "error": serializers.BooleanField(), "message": serializers.CharField(), }, ) }, ) def post(self, request, *args, **kwargs): params = request.data serializer = DocumentCreateSerializer(data=params, request_obj=request) if serializer.is_valid(): doc = serializer.save( created_by=request.profile.user, org=request.profile.org, document_file=request.FILES.get("document_file"), ) if params.get("shared_to"): assinged_to_list = params.get("shared_to") profiles = Profile.objects.filter( id__in=assinged_to_list, org=request.profile.org, is_active=True ) if profiles: doc.shared_to.add(*profiles) if params.get("teams"): teams_list = params.get("teams") teams = Teams.objects.filter(id__in=teams_list, org=request.profile.org) if teams: doc.teams.add(*teams) return Response( {"error": False, "message": "Document Created Successfully"}, status=status.HTTP_201_CREATED, ) return Response( {"error": True, "errors": serializer.errors}, status=status.HTTP_400_BAD_REQUEST, ) class DocumentDetailView(APIView): permission_classes = (IsAuthenticated, HasOrgContext) def get_object(self, pk): # Security fix: Filter by org to prevent cross-org enumeration return get_object_or_404(Document, id=pk, org=self.request.profile.org) @extend_schema( tags=["documents"], operation_id="documents_retrieve", parameters=swagger_params.organization_params, responses={ 200: inline_serializer( name="DocumentDetailResponse", fields={ "doc_obj": DocumentSerializer(), "file_type_code": serializers.CharField(), "users": ProfileSerializer(many=True), }, ) }, ) def get(self, request, pk, format=None): # get_object_or_404 now handles org filtering - returns 404 if not found/wrong org self.object = self.get_object(pk) if self.request.profile.role != "ADMIN" and not self.request.user.is_superuser: if not ( (self.request.profile == self.object.created_by) or (self.request.profile in self.object.shared_to.all()) ): return Response( { "error": True, "errors": "You do not have Permission to perform this action", }, status=status.HTTP_403_FORBIDDEN, ) profile_list = Profile.objects.filter(org=self.request.profile.org) if request.profile.role == "ADMIN" or request.user.is_superuser: profiles = profile_list.order_by("user__email") else: profiles = profile_list.filter(role="ADMIN").order_by("user__email") context = {} context.update( { "doc_obj": DocumentSerializer(self.object).data, "file_type_code": self.object.file_type()[1], "users": ProfileSerializer(profiles, many=True).data, } ) return Response(context, status=status.HTTP_200_OK) @extend_schema( tags=["documents"], operation_id="documents_destroy", parameters=swagger_params.organization_params, responses={ 200: inline_serializer( name="DocumentDeleteResponse", fields={ "error": serializers.BooleanField(), "message": serializers.CharField(), }, ) }, ) def delete(self, request, pk, format=None): document = self.get_object(pk) if not document: return Response( {"error": True, "errors": "Documdnt does not exist"}, status=status.HTTP_403_FORBIDDEN, ) if document.org != self.request.profile.org: return Response( {"error": True, "errors": "User company doesnot match with header...."}, status=status.HTTP_403_FORBIDDEN, ) if self.request.profile.role != "ADMIN" and not self.request.user.is_superuser: if ( self.request.profile != document.created_by ): # or (self.request.profile not in document.shared_to.all()): return Response( { "error": True, "errors": "You do not have Permission to perform this action", }, status=status.HTTP_403_FORBIDDEN, ) document.delete() return Response( {"error": False, "message": "Document deleted Successfully"}, status=status.HTTP_200_OK, ) @extend_schema( tags=["documents"], operation_id="documents_update", parameters=swagger_params.organization_params, request=DocumentEditSwaggerSerializer, responses={ 200: inline_serializer( name="DocumentUpdateResponse", fields={ "error": serializers.BooleanField(), "message": serializers.CharField(), }, ) }, ) def put(self, request, pk, format=None): self.object = self.get_object(pk) params = request.data if not self.object: return Response( {"error": True, "errors": "Document does not exist"}, status=status.HTTP_403_FORBIDDEN, ) if self.object.org != self.request.profile.org: return Response( {"error": True, "errors": "User company doesnot match with header...."}, status=status.HTTP_403_FORBIDDEN, ) if self.request.profile.role != "ADMIN" and not self.request.user.is_superuser: if not ( (self.request.profile == self.object.created_by) or (self.request.profile in self.object.shared_to.all()) ): return Response( { "error": True, "errors": "You do not have Permission to perform this action", }, status=status.HTTP_403_FORBIDDEN, ) serializer = DocumentCreateSerializer( data=params, instance=self.object, request_obj=request ) if serializer.is_valid(): doc = serializer.save( document_file=request.FILES.get("document_file"), status=params.get("status"), org=request.profile.org, ) doc.shared_to.clear() if params.get("shared_to"): assinged_to_list = params.get("shared_to") profiles = Profile.objects.filter( id__in=assinged_to_list, org=request.profile.org, is_active=True ) if profiles: doc.shared_to.add(*profiles) doc.teams.clear() if params.get("teams"): teams_list = params.get("teams") teams = Teams.objects.filter(id__in=teams_list, org=request.profile.org) if teams: doc.teams.add(*teams) return Response( {"error": False, "message": "Document Updated Successfully"}, status=status.HTTP_200_OK, ) return Response( {"error": True, "errors": serializer.errors}, status=status.HTTP_400_BAD_REQUEST, ) @extend_schema( tags=["documents"], parameters=swagger_params.organization_params, request=DocumentEditSwaggerSerializer, description="Partial Document Update", responses={ 200: inline_serializer( name="DocumentPatchResponse", fields={ "error": serializers.BooleanField(), "message": serializers.CharField(), }, ) }, ) def patch(self, request, pk, format=None): """Handle partial updates to a document.""" self.object = self.get_object(pk) params = request.data if not self.object: return Response( {"error": True, "errors": "Document does not exist"}, status=status.HTTP_403_FORBIDDEN, ) if self.object.org != self.request.profile.org: return Response( { "error": True, "errors": "User company does not match with header....", }, status=status.HTTP_403_FORBIDDEN, ) if self.request.profile.role != "ADMIN" and not self.request.user.is_superuser: if not ( (self.request.profile == self.object.created_by) or (self.request.profile in self.object.shared_to.all()) ): return Response( { "error": True, "errors": "You do not have Permission to perform this action", }, status=status.HTTP_403_FORBIDDEN, ) serializer = DocumentCreateSerializer( data=params, instance=self.object, request_obj=request, partial=True ) if serializer.is_valid(): doc = serializer.save( org=request.profile.org, ) return Response( {"error": False, "message": "Document Updated Successfully"}, status=status.HTTP_200_OK, ) return Response( {"error": True, "errors": serializer.errors}, status=status.HTTP_400_BAD_REQUEST, )