11import logging
2+ from typing import Annotated
23
34from django .contrib .auth import get_user_model
4- from django .db .models import Q
5+ from django .core .exceptions import ObjectDoesNotExist
6+ from django .shortcuts import get_object_or_404
7+ from django .db .models import Q , QuerySet
58from django_filters import rest_framework as filters
69from drf_yasg import openapi
710from drf_yasg .utils import swagger_auto_schema
1518from core .serializers import SetLikedSerializer
1619from core .services import add_view , set_like
1720from partner_programs .models import PartnerProgram , PartnerProgramUserProfile
21+ from projects .exceptions import CollaboratorDoesNotExist
1822from projects .filters import ProjectFilter
1923from projects .constants import VERBOSE_STEPS
2024from projects .helpers import (
2125 get_recommended_users ,
2226 check_related_fields_update ,
2327 update_partner_program ,
2428)
25- from projects .models import Project , Achievement , ProjectNews
29+ from projects .models import Project , Achievement , ProjectNews , Collaborator
2630from projects .pagination import ProjectNewsPagination , ProjectsPagination
2731from projects .permissions import (
2832 IsProjectLeaderOrReadOnlyForNonDrafts ,
4347from users .models import LikesOnProject
4448from users .serializers import UserListSerializer
4549from vacancy .models import VacancyResponse
46- from vacancy .serializers import VacancyResponseListSerializer
50+ from vacancy .serializers import VacancyResponseFullFileInfoListSerializer
4751
4852logger = logging .getLogger ()
4953
@@ -247,15 +251,48 @@ def post(self, request, pk: int):
247251 return Response (status = 200 )
248252
249253 def delete (self , request , pk : int ):
250- """delete collaborators from the project"""
251- m2m_manager = self .get_object ().collaborators
252- serializer = self .get_serializer (data = request .data )
253- serializer .is_valid (raise_exception = True )
254- collaborators = serializer .validated_data ["collaborators" ]
255- for user in collaborators :
256- # note: doesn't raise an error when we try to delete someone who isn't a collaborator
257- m2m_manager .remove (user )
258- return Response (status = 200 )
254+ """delete collaborator from project"""
255+ requested_collab_id : int = int (self .request .query_params .get ("id" ))
256+
257+ project_id , leader_id = self ._project_data (pk )
258+ existing_collab_id = self ._collabs_queryset (
259+ project_id , requested_collab_id , leader_id
260+ )
261+
262+ if leader_id == requested_collab_id :
263+ return Response (
264+ {
265+ "error" : f"User with id: { leader_id } is a leader of a project. "
266+ f"Be careful not to delete yourself from a project!"
267+ },
268+ status = status .HTTP_422_UNPROCESSABLE_ENTITY ,
269+ )
270+ if not existing_collab_id :
271+ return Response (
272+ {
273+ "error" : f"User with id: { requested_collab_id } are not part of this project."
274+ },
275+ status = status .HTTP_422_UNPROCESSABLE_ENTITY ,
276+ )
277+
278+ existing_collab_id .delete ()
279+ return Response (status = 204 )
280+
281+ def _project_data (
282+ self , project_pk : int
283+ ) -> tuple [Annotated [int , "ID проекта" ], Annotated [int , "ID лидера проекта" ]]:
284+ project = get_object_or_404 (
285+ Project .objects .select_related ("leader" ), id = project_pk
286+ )
287+ return project .id , project .leader .id
288+
289+ @staticmethod
290+ def _collabs_queryset (project_id : int , requested_id : int , leader_id : int ) -> QuerySet :
291+ return Collaborator .objects .exclude (
292+ user__id = leader_id
293+ ).get ( # чтоб случайно лидер сам себя не удалил
294+ user__id = requested_id , project__id = project_id
295+ )
259296
260297
261298class ProjectSteps (APIView ):
@@ -281,7 +318,7 @@ class AchievementDetail(generics.RetrieveUpdateDestroyAPIView):
281318
282319
283320class ProjectVacancyResponses (generics .GenericAPIView ):
284- serializer_class = VacancyResponseListSerializer
321+ serializer_class = VacancyResponseFullFileInfoListSerializer
285322 permission_classes = [IsAuthenticated ]
286323
287324 def get_queryset (self ):
@@ -461,3 +498,144 @@ def post(self, request, project_pk):
461498 return Response (
462499 {"detail" : "Subscriber was successfully removed" }, status = status .HTTP_200_OK
463500 )
501+
502+
503+
504+ class SwitchLeaderRole (generics .GenericAPIView ):
505+ permission_classes = [IsProjectLeader ]
506+ queryset = Project .objects .all ().select_related ("leader" )
507+
508+ def _get_new_leader (self , user_id : int , project : Project ) -> Collaborator :
509+ try :
510+ return Collaborator .objects .select_related ("user" ).get (
511+ user_id = user_id , project = project
512+ )
513+ except ObjectDoesNotExist :
514+ raise CollaboratorDoesNotExist (
515+ f"""Collaborator with user_id: { user_id } does not exist. Either user_id is not correct, or project_id
516+ is not correct, or try adding this user to a project (as collaborator) before making them a leader. """
517+ )
518+
519+ def patch (self , request , pk : int ):
520+ project = self .get_object ()
521+
522+ new_leader_id = int (request .data ["new_leader_id" ])
523+ new_leader = self ._get_new_leader (new_leader_id , project )
524+
525+ if project .leader .id == new_leader_id :
526+ return Response (
527+ {"error" : "User is already a leader of a project" },
528+ status = status .HTTP_422_UNPROCESSABLE_ENTITY ,
529+ )
530+
531+ project .leader = new_leader .user
532+ project .save ()
533+ return Response (status = 204 )
534+
535+
536+ class LeaveProject (generics .GenericAPIView ):
537+ permission_classes = [IsAuthenticated ]
538+
539+ def delete (self , request , project_pk : int ) -> Response :
540+ current_user_id = self .request .user .id
541+ collaborator = get_object_or_404 (
542+ Collaborator .objects .all (),
543+ project_id = project_pk ,
544+ user_id = current_user_id ,
545+ )
546+ project = Project .objects .select_related ("leader" ).get (id = project_pk )
547+ if project .leader .id == current_user_id :
548+ return Response (
549+ {
550+ "error" : "You can't leave if you are a leader of a project. "
551+ "Please, switch leadership!"
552+ },
553+ status = status .HTTP_422_UNPROCESSABLE_ENTITY ,
554+ )
555+ collaborator .delete ()
556+ return Response (status = 204 )
557+
558+
559+ class DeleteProjectCollaborators (generics .GenericAPIView ):
560+ permission_classes = [IsProjectLeader ]
561+
562+ def _project_data (
563+ self , project_pk : int
564+ ) -> tuple [Annotated [int , "ID проекта" ], Annotated [int , "ID лидера проекта" ]]:
565+ project = get_object_or_404 (
566+ Project .objects .select_related ("leader" ), id = project_pk
567+ )
568+ return project .id , project .leader .id
569+
570+ @staticmethod
571+ def _collabs_queryset (project_id : int , requested_id : int , leader_id : int ) -> QuerySet :
572+ return Collaborator .objects .exclude (
573+ user__id = leader_id
574+ ).get ( # чтоб случайно лидер сам себя не удалил
575+ user__id = requested_id , project__id = project_id
576+ )
577+
578+ def delete (self , request , project_pk : int ) -> Response :
579+ requested_collab_id : int = int (self .request .query_params .get ("id" ))
580+
581+ project_id , leader_id = self ._project_data (project_pk )
582+ existing_collab_id = self ._collabs_queryset (
583+ project_id , requested_collab_id , leader_id
584+ )
585+
586+ if leader_id == requested_collab_id :
587+ return Response (
588+ {
589+ "error" : f"User with id: { leader_id } is a leader of a project. "
590+ f"Be careful not to delete yourself from a project!"
591+ },
592+ status = status .HTTP_422_UNPROCESSABLE_ENTITY ,
593+ )
594+ if not existing_collab_id :
595+ return Response (
596+ {
597+ "error" : f"User with id: { requested_collab_id } are not part of this project."
598+ },
599+ status = status .HTTP_422_UNPROCESSABLE_ENTITY ,
600+ )
601+
602+ existing_collab_id .delete ()
603+ return Response (status = 204 )
604+
605+
606+ class SwitchLeaderRole (generics .GenericAPIView ):
607+ permission_classes = [IsProjectLeader ]
608+ queryset = Project .objects .all ().select_related ("leader" )
609+
610+ @staticmethod
611+ def _get_new_leader (user_id : int , project : Project ) -> Collaborator :
612+ try :
613+ return Collaborator .objects .select_related ("user" ).get (
614+ user_id = user_id , project = project
615+ )
616+ except ObjectDoesNotExist :
617+ raise CollaboratorDoesNotExist (
618+ f"""Collaborator with user_id: { user_id } does not exist. Either user_id is not correct, or project_id
619+ is not correct, or try adding this user to a project (as collaborator) before making them a leader. """
620+ )
621+
622+ @staticmethod
623+ def _get_project (project_pk : int ) -> Project :
624+ return get_object_or_404 (Project .objects .all (), id = project_pk )
625+
626+ def patch (self , request , project_pk : int , user_to_leader_pk : int ) -> Response :
627+ project = self ._get_project (project_pk )
628+
629+ new_leader_id = user_to_leader_pk
630+
631+ if project .leader .id == new_leader_id :
632+ return Response (
633+ {"error" : "User is already a leader of a project" },
634+ status = status .HTTP_422_UNPROCESSABLE_ENTITY ,
635+ )
636+
637+ new_leader = self ._get_new_leader (new_leader_id , project )
638+
639+ project .leader = new_leader .user
640+ project .save ()
641+ return Response (status = 204 )
0 commit comments